Solved

Approach to understand what an already written class is doing

Posted on 2015-01-02
3
72 Views
Last Modified: 2015-02-10
Hi,
I  wanted to understand whats the purpose of following java class and whats it doing. But i found it difficult as its written by someone else. Are there any general steps which one can follow to try to understand a java class as there are sometimes lot of complex information hidden in it. whats the best way to decipher a class. The class which i wanted to decipher is the following :
package to.talk.stream;

import android.content.Context;

import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.util.concurrent.Callable;

import olympus.clients.commons.businessObjects.Jid;
import to.talk.doorProxy.DoorClient;
import to.talk.doorProxy.protocol.DoorContract.Type;
import to.talk.doorProxy.protocol.stream.DoorProtocolHandler;
import to.talk.doorProxy.protocol.stream.exception.NoSuchStreamException;
import to.talk.doorProxy.protocol.stream.exception.StreamAlreadyAddedException;
import to.talk.logging.Logger;
import to.talk.logging.LoggerFactory;
import to.talk.stream.StreamStatus.StreamDisconnectionReason;
import to.talk.stream.config.StreamConfig;
import to.talk.stream.exception.BadCredsException;
import to.talk.stream.exception.ConnectionUnavailableException;
import to.talk.stream.exception.InsuffCredException;
import to.talk.stream.loginretrial.AsyncRetryingManager;
import to.talk.stream.loginretrial.LoginRetrialStrategy;
import to.talk.stream.loginretrial.RetryingResultCallback;
import to.talk.stream.objects.LoginError;
import to.talk.stream.objects.LoginError.LoginFailureReason;
import to.talk.stream.objects.StreamInfoStore;
import to.talk.stream.packets.IPacket;
import to.talk.stream.packets.IncomingPacket;
import to.talk.stream.util.IContext;
import to.talk.stream.xml.PseudoConnectionPacketMaker;
import to.talk.stream.xml.StreamXmlPacketParser;
import to.talk.stream.xml.XMLUtils;
import to.talk.utils.event.Event;
import to.talk.utils.event.EventHandler;

import static to.talk.stream.StreamStatus.StreamDisconnectionReason.BAD_CREDENTIALS;
import static to.talk.stream.StreamStatus.StreamDisconnectionReason.INSUFFICIENT_CREDENTIALS;
import static to.talk.stream.StreamStatus.StreamDisconnectionReason.NO_CONNECTIVITY;
import static to.talk.stream.StreamStatus.StreamDisconnectionReason.STREAM_ENDED;
import static to.talk.stream.StreamStatus.StreamDisconnectionReason.STREAM_ERROR;
import static to.talk.stream.StreamStatus.StreamDisconnectionReason.TEMPORARY_UNAVAILABILITY_LOGIN_SCHEDULED;
import static to.talk.stream.StreamStatus.StreamState.CANT_CONNECT;
import static to.talk.stream.StreamStatus.StreamState.CONNECTED;
import static to.talk.stream.StreamStatus.StreamState.CONNECTING;
import static to.talk.stream.StreamStatus.StreamState.DISCONNECTED;
import static to.talk.stream.objects.LoginError.LoginFailureReason.DOOR_NOT_CONNECTED;
import static to.talk.stream.objects.LoginError.LoginFailureReason.INVALID_CREDS;


public class StreamClient {
    private static final Logger _logger = LoggerFactory
            .getTrimmer(StreamClient.class, "stream-client");
    private final StreamConfig _config;
    private final StreamInfoStore _userStreamInfo;
    private DoorProtocolHandler _doorStreamHandler;
    //Events
    private Event<IncomingPacket> _onPacketReceived = new Event<IncomingPacket>(
            "on-packet-received");
    private Event<StreamStatus> _onStatusChanged = new Event<StreamStatus>("status-changed");
    private Event<Credential> _onBadCredentials = new Event<Credential>("bad-credential");
    private volatile StreamStatus _streamStatus;
    private AsyncRetryingManager<Void, LoginError> _asyncRetryingManager;
    private LoginRetrialStrategy _loginRetrialStrategy;

    public StreamClient(Context context, StreamConfig config) {
        _config = config;
        _userStreamInfo = new StreamInfoStore(context);

        changeStatus(DISCONNECTED, NO_CONNECTIVITY);

        StreamXmlPacketParser streamXmlPacketParser = new StreamXmlPacketParser();
        DoorClient doorClient = DoorClient.createInstance(context, config);

        attachPacketParserEventHandlers(streamXmlPacketParser);
        attachDoorClientEventHandlers(doorClient, streamXmlPacketParser);

        startDoorSession(doorClient);
    }

    public boolean isConnected() {
        return _streamStatus.getState() == CONNECTED;
    }

    public void addStatusChangeListener(EventHandler<StreamStatus> handler) {
        _onStatusChanged.addEventHandler(handler);
    }

    public void addBadCredentialListener(EventHandler<Credential> handler) {
        _onBadCredentials.addEventHandler(handler);
    }

    public void addStreamPacketListener(EventHandler<IncomingPacket> handler) {
        _onPacketReceived.addEventHandler(handler);
    }

    public void removeStatusChangeListener(EventHandler<StreamStatus> handler) {
        _onStatusChanged.removeEventHandler(handler);
    }

    public void removeBadCredentialListener(EventHandler<Credential> handler) {
        _onBadCredentials.removeEventHandler(handler);
    }

    public void removeStreamPacketListener(EventHandler<IncomingPacket> handler) {
        _onPacketReceived.removeEventHandler(handler);
    }

    public StreamStatus getStreamStatus() {
        return _streamStatus;
    }

    private void changeStatus(StreamStatus.StreamState state,
                              StreamStatus.StreamDisconnectionReason disconnectionReason) {
        final StreamStatus newStatus = new StreamStatus(state, disconnectionReason);
        _logger.debug("Inside change state. new status: {} , previous: {}", newStatus, _streamStatus);

        if (isStatusChangePossible(newStatus)) {
            _streamStatus = newStatus;
            _onStatusChanged.raiseEvent(newStatus);
        }
    }

    private boolean isStatusChangePossible(StreamStatus newStatus) {
        if (newStatus.getState() != DISCONNECTED && newStatus.getState() != CANT_CONNECT &&
                newStatus.getDisconnectionReason() != null) {
            _logger.warn("Trying to set non-null disconnectionReason while state being set is " +
                    newStatus.getState() + ",which is not possible. Ignoring it.");
            return false;
        }
        return !newStatus.equals(_streamStatus);
    }

    private void attachPacketParserEventHandlers(StreamXmlPacketParser streamXmlPacketParser) {
        streamXmlPacketParser.onStreamAdded.addEventHandler(new EventHandler<Void>() {
            @Override
            public void run(Void v) {
                handleStreamConnect();
            }
        });
        streamXmlPacketParser.onStreamReclaimed.addEventHandler(new EventHandler<Void>() {
            @Override
            public void run(Void v) {
                handleStreamConnect();
            }
        });
        streamXmlPacketParser.onStreamFailed.addEventHandler(new EventHandler<LoginError>() {
            @Override
            public void run(LoginError result) {
                _asyncRetryingManager.failure(result);
            }
        });
        streamXmlPacketParser.onStreamEnded.addEventHandler(new EventHandler<Void>() {
            @Override
            public void run(Void result) {
                handleStreamEnd(STREAM_ENDED);
            }
        });
        streamXmlPacketParser.onStreamError.addEventHandler(new EventHandler<Void>() {
            @Override
            public void run(Void result) {
                handleStreamError();
            }
        });
        streamXmlPacketParser.onStreamPacketReceived.addEventHandler(new EventHandler<IPacket>() {
            @Override
            public void run(IPacket packet) {
                if (isConnected()) {
                    IncomingPacket incomingPacket = new IncomingPacket(getEntity().getFullJid(),
                            packet);
                    try {
                        _onPacketReceived.raiseEvent(incomingPacket);
                    } catch (Exception ex) {
                        _logger.error("Misbehaving handler for incoming packet:{} ",
                                XMLUtils.toXML(incomingPacket), ex);

                    }
                }
            }
        });
    }

    private void attachDoorClientEventHandlers(DoorClient doorClient,
                                               final StreamXmlPacketParser streamXmlPacketParser) {
        _doorStreamHandler = doorClient.getStreamProtocolHandler();
        _doorStreamHandler.addListener(new DoorProtocolHandler.Listener() {
            @Override
            public void onStreamDisconnectedWithMH(Jid jid) {
                if (jid.equals(getEntity())) {
                    handleStreamDisconnection();

                }
            }

            @Override
            public void onStreamPacketReceived(Jid jid, String body) {
                if (jid.equals(getEntity())) {
                    streamXmlPacketParser.parseBody(body);
                }
            }

            @Override
            public void onAllStreamsDisconnected() {
                _logger.debug("on all streams disconnected");
                String userJid = getEntityAsString();
                if (userJid != null) {
                    changeStatus(DISCONNECTED, NO_CONNECTIVITY);
                }
            }

            @Override
            public void onSessionStarted() {
                _logger.debug("on door session started");
                login();
            }
        });
    }

    private void startDoorSession(DoorClient doorClient) {
        doorClient.startSession();
    }

    public void resumeSession() {
        DoorClient.getInstance().resumeSession();
    }

    private String getEntityAsString() {
        return
                _userStreamInfo.getUserJid() != null ? _userStreamInfo.getUserJid().getFullJid() : null;
    }

    private String getStreamId() {
        return _userStreamInfo.getStreamId();
    }

    private Credential getCredentials() {
        return _userStreamInfo.getCredentials();
    }

    private void handleStreamConnect() {
        _logger.debug("handle stream connect: {}", getStreamId());
        _asyncRetryingManager.success(null);
        changeStatus(CONNECTED, null);
    }

    private void handleStreamError() {
        _logger.debug("handle stream error");
        _logger.debug("StreamInfo: {}", _userStreamInfo);
        if (_streamStatus.getState() == CONNECTING) {
            _asyncRetryingManager.failure(new LoginError(LoginFailureReason.STREAM_ERROR));
        } else {
            if (isConnected()) {
                try {
                    _doorStreamHandler.sendStreamPacket(getEntity(), PseudoConnectionPacketMaker
                            .getStreamEndPacketString());
                    _doorStreamHandler.endStream(getEntity());
                } catch (NoSuchStreamException e) {
                    //Dont do anything if stream has already been removed.
                }
            }
            handleStreamEnd(STREAM_ERROR);
        }
    }

    public Jid getEntity() {
        if (getCredentials() != null) {
            return _userStreamInfo.getUserJid();
        }
        return null;
    }

    public String getAuthToken() {
        if (getCredentials() != null) {
            return getCredentials().getToken();
        }
        return null;
    }


    public ListenableFuture<Void> send(final IPacket packet) {
        return send(packet, Type.STREAM_PACKET);
    }

    public ListenableFuture<Void> send(final IPacket packet, Type packetType) {
        SettableFuture<Void> future = SettableFuture.create();
        if (isConnected()) {
            String packetString = XMLUtils.toXML(packet);
            try {
                return _doorStreamHandler
                        .sendStreamPacket(getEntity(), packetType, packetString);
            } catch (NoSuchStreamException e) {
                String errorMessage = "Stream " + getStreamId() + " does not exist";
                reportErrorInSendingPacket(errorMessage, packet);
                future.setException(e);
            }
        } else {
            String errorMessage = "Stream " + getStreamId() + " does not exist";
            reportErrorInSendingPacket(errorMessage, packet);
            future.setException(new NoSuchStreamException(getStreamId(), null));
        }
        return future;
    }


    private static void reportErrorInSendingPacket(String message, IPacket packet) {
        _logger.warn(message + " packet type: " + packet.getName());
    }

    public ListenableFuture<Void> logout() {
        final SettableFuture<Void> future = SettableFuture.create();
        _logger.debug("logout : {}");
        if (_streamStatus.getState() != DISCONNECTED && _streamStatus.getState() != CANT_CONNECT) {
            try {

                ListenableFuture<Void> sendStreamPacket = _doorStreamHandler.sendStreamPacket(getEntity(), Type.STREAM_PACKET,
                        PseudoConnectionPacketMaker
                                .getStreamEndPacketString()
                );

                ListenableFuture<Void> endStreamFuture = Futures.transform(sendStreamPacket, new AsyncFunction<Void, Void>() {
                    @Override
                    public ListenableFuture<Void> apply(Void aVoid) throws Exception {
                        return _doorStreamHandler.endStream(getEntity());
                    }
                });

                Futures.addCallback(endStreamFuture, new FutureCallback<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {

                        changeStatus(DISCONNECTED, STREAM_ENDED);
                        future.set(null);
                    }

                    @Override
                    public void onFailure(Throwable throwable) {
                        future.setException(throwable);

                    }
                });
            } catch (NoSuchStreamException e) {
                future.set(null);
            }
        }
        removeStreamInfo();
        clearLoginRetrials();
        changeStatus(CANT_CONNECT, INSUFFICIENT_CREDENTIALS);
        return future;
    }

    private void handleStreamDisconnection() {
        _logger.debug("handle stream disconnection");
        _logger.debug("StreamInfo: {}", _userStreamInfo);
        changeStatus(DISCONNECTED, StreamDisconnectionReason.DOOR_DISCONNECTED_FROM_MH);
        login();
    }

    private void handleStreamEnd(StreamDisconnectionReason streamDisconnectionReason) {
        _logger.debug("handle stream end");
        _logger.debug("StreamInfo: {}", _userStreamInfo);
        changeStatus(DISCONNECTED, streamDisconnectionReason);
        removeStreamId();
        login();
    }

    private synchronized void removeStreamInfo() {
        _userStreamInfo.clear();
    }

    private synchronized void removeStreamId() {
        _userStreamInfo.clearId();
    }

    private ListenableFuture<Void> sendStreamStart() {
        SettableFuture<Void> future = SettableFuture.create();
        synchronized (_userStreamInfo) {
            if (_streamStatus.getState() != DISCONNECTED &&
                    _streamStatus.getState() != CANT_CONNECT) {
                _logger
                        .error("Ignoring `send stream start` as current state is {}.", _streamStatus);
                future.setException(new Throwable("current state connected"));
                return future;
            }
            changeStatus(CONNECTING, null);
        }
        try {
            String openingStreamTag = PseudoConnectionPacketMaker
                    .getOpeningStreamTag(getStreamId(), getCredentials(), _config);
            _logger.debug("Opening Stream  {} ", openingStreamTag);
            return _doorStreamHandler
                    .startStream(getEntity(), openingStreamTag);
        } catch (StreamAlreadyAddedException e) {
            future.setException(e);
            _logger.error(
                    "duplicate stream. Stream Id {} is already present. This exception shud never come.",
                    e);
        }
        return future;
    }

    private ListenableFuture<Void> login() {

        Credential credentials = getCredentials();
        if (credentials == null) {
            changeStatus(CANT_CONNECT, INSUFFICIENT_CREDENTIALS);
            _logger.warn("Ignoring attempt to login as streamInfo is {}.", _userStreamInfo);
            return Futures.immediateFailedFuture(new InsuffCredException());
        } else {
            return login(credentials);
        }
    }

    public ListenableFuture<Void> login(Credential credential) {

        _logger.debug("login : {}", credential);
        if (isLoginPossible(credential)) {
            String streamId = null;
            if (credsSameAsCurrent(credential)) {
                streamId = getStreamId();
            }
            if (streamId == null) {
                final String fullJid = credential.getFullJid();
                streamId = StreamInfoStore.createNewStreamIdFromUserJid(fullJid);
                _userStreamInfo.reset(streamId, credential);
            }
            return scheduleLogin();
        } else {
            return Futures.immediateFailedFuture(new StreamAlreadyAddedException(credential.getFullJid()));
        }
    }

    private ListenableFuture<Void> scheduleLogin() {

        final SettableFuture<Void> future = SettableFuture.create();
        clearLoginRetrials();
        _loginRetrialStrategy = new LoginRetrialStrategy();
        _asyncRetryingManager = new AsyncRetryingManager<Void, LoginError>(_loginRetrialStrategy) {
            @Override
            public void failure(LoginError result) {
                final LoginFailureReason failureReason = result.getLoginFailureReason();
                switch (failureReason) {
                    case TEMP_AUTH_FAILURE:
                        changeStatus(DISCONNECTED, StreamDisconnectionReason.TEMPORARY_UNAVAILABILITY_LOGIN_SCHEDULED);
                        break;
                    case INVALID_CREDS:
                        changeStatus(DISCONNECTED, StreamDisconnectionReason.BAD_CREDENTIALS);
                        break;
                    case DOOR_NOT_CONNECTED:
                        changeStatus(DISCONNECTED, StreamDisconnectionReason.NO_CONNECTIVITY);
                        break;
                    case STREAM_ERROR:
                        changeStatus(DISCONNECTED, StreamDisconnectionReason.STREAM_ERROR);
                        break;
                    case UNKNOWN:
                        changeStatus(DISCONNECTED, null);
                        break;
                }
                super.failure(result);
            }
        };
        _asyncRetryingManager.assignWork(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                _logger.debug("send stream start: {}");
                ListenableFuture<Void> future = sendStreamStart();
                Futures.addCallback(future, new FutureCallback<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {

                    }

                    @Override
                    public void onFailure(Throwable throwable) {

                        _logger.debug("callback failed, calling retrying manager's failure");
                        _asyncRetryingManager.failure(new LoginError(DOOR_NOT_CONNECTED));
                    }
                });
                return null;
            }
        });
        _asyncRetryingManager.setExceptionHandlingContext(new IContext() {
            @Override
            public void onException() {
                clearLoginRetrials();
                login();
            }
        });
        _asyncRetryingManager.setOnResultCallback(new RetryingResultCallback<Void, LoginError>() {
            @Override
            public void success(Void result) {
                clearLoginRetrials();
                future.set(null);
            }

            @Override
            public void retrialStopped(LoginError loginError) {
                clearLoginRetrials();
                if (getStreamStatus().getState() != DISCONNECTED &&
                        getStreamStatus().getState() != CANT_CONNECT) {
                    changeStatus(DISCONNECTED, null);
                }
                final LoginFailureReason failureReason = loginError.getLoginFailureReason();
                if (failureReason != INVALID_CREDS && failureReason != DOOR_NOT_CONNECTED) {
                    _logger.error(
                            "login retrial stopped for reason {}. We only expected stoppage for INVALID_CREDS or DOOR_NOT_CONNECTED");
                    //Currently loginRetrialStrategy only stops retrying in case of INVALID_CREDS
                    login();
                } else if (failureReason == DOOR_NOT_CONNECTED) {
                    future.setException(new ConnectionUnavailableException());
                }

                if (failureReason == INVALID_CREDS) {
                    final Credential badCredentials = getCredentials();
                    removeStreamInfo();
                    changeStatus(CANT_CONNECT, BAD_CREDENTIALS);
                    _onBadCredentials.raiseEvent(badCredentials);
                    future.setException(new BadCredsException());
                }
            }

            @Override
            public void retrialScheduled(long scheduledTime) {
                _logger.debug("Retrial scheduled for stream {} login on  {} ", _userStreamInfo,
                        scheduledTime);
                if (getStreamStatus().getState() != DISCONNECTED) {
                    _logger.warn(
                            "Stream state was not disconnected while account is still retrying. Resetting state to disconnected.");
                    changeStatus(DISCONNECTED, null);
                }
            }
        });
        _asyncRetryingManager.start();
        return future;
    }

    private void clearLoginRetrials() {
        if (_asyncRetryingManager != null) {
            _asyncRetryingManager.stop();
            _asyncRetryingManager = null;
        }
    }

    private boolean isLoginPossible(Credential credential) {
        switch (_streamStatus.getState()) {
            case CONNECTING:
            case CONNECTED:
                if (credsSameAsCurrent(credential)) {
                    _logger.warn("stream already exists: {}", _userStreamInfo);
                    return false;
                }
                logout();
                return true;
            case DISCONNECTED:
                if (credsSameAsCurrent(credential) &&
                        _streamStatus.getDisconnectionReason() == TEMPORARY_UNAVAILABILITY_LOGIN_SCHEDULED) {
                    _logger.warn("stream already exists and is temporarily unavailable: {}",
                            _userStreamInfo);
                    return false;
                }
            default:
                return true;
        }
    }

    private boolean credsSameAsCurrent(Credential credential) {
        return credential.getFullJid().equals(getEntityAsString());
    }
}

Open in new window


How should one go about it ?

Thanks
0
Comment
Question by:Rohit Bajaj
  • 2
3 Comments
 

Author Comment

by:Rohit Bajaj
ID: 40527777
The attachment contains the java files which are used by the above code.
src.zip
0
 
LVL 26

Accepted Solution

by:
dpearson earned 500 total points
ID: 40528794
The first place to start is to look at the data, which in this case are these members:

    private static final Logger _logger = LoggerFactory
            .getTrimmer(StreamClient.class, "stream-client");
    private final StreamConfig _config;
    private final StreamInfoStore _userStreamInfo;
    private DoorProtocolHandler _doorStreamHandler;
    //Events
    private Event<IncomingPacket> _onPacketReceived = new Event<IncomingPacket>(
            "on-packet-received");
    private Event<StreamStatus> _onStatusChanged = new Event<StreamStatus>("status-changed");
    private Event<Credential> _onBadCredentials = new Event<Credential>("bad-credential");
    private AsyncRetryingManager<Void, LoginError> _asyncRetryingManager;
    private LoginRetrialStrategy _loginRetrialStrategy;


Of those there's a series of events and some logging - which aren't likely central to what's going on.

These look to be the key pieces of data:

    private final StreamConfig _config;
    private final StreamInfoStore _userStreamInfo;
    private DoorProtocolHandler _doorStreamHandler;

    private volatile StreamStatus _streamStatus;

and from this you can get the general idea that this class is for handling a "stream" (not sure where this stream is coming or going - but it's a stream of data).

If you next look at the constructor:

    public StreamClient(Context context, StreamConfig config) {
        _config = config;
        _userStreamInfo = new StreamInfoStore(context);

        changeStatus(DISCONNECTED, NO_CONNECTIVITY);

        StreamXmlPacketParser streamXmlPacketParser = new StreamXmlPacketParser();
        DoorClient doorClient = DoorClient.createInstance(context, config);

        attachPacketParserEventHandlers(streamXmlPacketParser);
        attachDoorClientEventHandlers(doorClient, streamXmlPacketParser);

        startDoorSession(doorClient);
    }

You can see that there's a StreamXmlPacketParser so it looks like the data coming over the stream is probably XML data.

So it looks like this is a class for managing some sort of connection to get a stream and then parsing it as XML.

Does that get you started?

Doug
0
 

Author Comment

by:Rohit Bajaj
ID: 40540096
HI,
That was helpful but i need to understand more detailed working of streamClient as i need to refactor it.
I am attaching some java files which are used in the StreamClient class. Please let me know your views on the eact working. One of the specific thing i want to understand is the use of     private Map<Jid, String> _jidStreamIdMap;  in DoorProtocolHandler.java . whats the use of streamID and how its assigning it.

Also i can see the API for sending. Its using socket. But how the messages are received. how the listeners like
    public Event<DoorPacket> onJSONMessageReceived;
in IDConnectionManager.java  work ?

Thanks
Archive.zip
0

Featured Post

How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

Join & Write a Comment

Suggested Solutions

A short article about a problem I had getting the GPS LocationListener working.
Whether you've completed a degree in computer sciences or you're a self-taught programmer, writing your first lines of code in the real world is always a challenge. Here are some of the most common pitfalls for new programmers.
Viewers learn how to read error messages and identify possible mistakes that could cause hours of frustration. Coding is as much about debugging your code as it is about writing it. Define Error Message: Line Numbers: Type of Error: Break Down…
This video teaches viewers about errors in exception handling.

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now