Solved

Approach to understand what an already written class is doing

Posted on 2015-01-02
3
81 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 27

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

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

A short article about a problem I had getting the GPS LocationListener working.
Although it can be difficult to imagine, someday your child will have a career of his or her own. He or she will likely start a family, buy a home and start having their own children. So, while being a kid is still extremely important, it’s also …
This video teaches viewers about errors in exception handling.

713 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