Java tutorial
/* * Copyright (C) 2014 Eric Butler * * 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 com.tapchatapp.android.client.model; import android.content.Context; import android.content.Intent; import android.text.TextUtils; import android.util.Log; import com.google.common.collect.ImmutableMap; import com.tapchatapp.android.R; import com.tapchatapp.android.app.activity.InvalidConnectionCertActivity; import com.tapchatapp.android.app.event.BufferAddedEvent; import com.tapchatapp.android.app.event.BufferChangedEvent; import com.tapchatapp.android.app.event.BufferRemovedEvent; import com.tapchatapp.android.app.event.ConnectionChangedEvent; import com.tapchatapp.android.client.MessageHandler; import com.tapchatapp.android.app.TapchatApp; import com.tapchatapp.android.client.TapchatService; import com.tapchatapp.android.client.message.BufferArchivedMessage; import com.tapchatapp.android.client.message.BufferEventMessage; import com.tapchatapp.android.client.message.BufferUnarchivedMessage; import com.tapchatapp.android.client.message.ConnectedMessage; import com.tapchatapp.android.client.message.ConnectingCancelledMessage; import com.tapchatapp.android.client.message.ConnectingFailedMessage; import com.tapchatapp.android.client.message.ConnectingFinishedMessage; import com.tapchatapp.android.client.message.ConnectingMessage; import com.tapchatapp.android.client.message.ConnectingRetryMessage; import com.tapchatapp.android.client.message.EndOfBacklogMessage; import com.tapchatapp.android.client.message.InvalidCertMessage; import com.tapchatapp.android.client.message.MakeBufferMessage; import com.tapchatapp.android.client.message.MakeServerMessage; import com.tapchatapp.android.client.message.Message; import com.tapchatapp.android.client.message.OpenBufferMessage; import com.tapchatapp.android.client.message.ResponseMessage; import com.tapchatapp.android.client.message.ServerDetailsChangedMessage; import com.tapchatapp.android.client.message.SocketClosedMessage; import com.tapchatapp.android.client.message.WaitingToRetryMessage; import com.tapchatapp.android.client.message.YouNickchangeMessage; import com.tapchatapp.android.client.message.request.AcceptCertMessage; import com.tapchatapp.android.client.message.request.DeleteBufferMessage; import com.tapchatapp.android.client.message.request.DeleteConnectionMessage; import com.tapchatapp.android.client.message.request.DisconnectMessage; import com.tapchatapp.android.client.message.request.EditServerMessage; import com.tapchatapp.android.client.message.request.JoinMessage; import com.tapchatapp.android.client.message.request.PartMessage; import com.tapchatapp.android.client.message.request.ReconnectMessage; import com.tapchatapp.android.client.message.request.SayMessage; import org.json.JSONException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.TreeMap; public class Connection { public static final int STATE_DISCONNECTED = 0; public static final int STATE_CONNECTING = 1; public static final int STATE_RETRYING = 2; public static final int STATE_CONNECTED = 3; public static final Comparator<Connection> COMPARATOR = new Comparator<Connection>() { @Override public int compare(Connection connection, Connection connection2) { int result = connection.getName().compareToIgnoreCase(connection2.getName()); if (result == 0) { result = connection.getHostName().compareToIgnoreCase(connection2.getHostName()); } return result; } }; private static final String TAG = "Connection"; private TapchatService mService; private boolean mExists = false; private boolean mIsBacklog = true; private final Map<Long, Buffer> mBuffers = Collections.synchronizedMap(new TreeMap<Long, Buffer>()); private ConsoleBuffer mConsoleBuffer; private int mState; private String mName; private long mId; private String mNick; private boolean mSSL; private String mHostName; private String mRealName; private int mPort; private String mPassword; private String mPendingOpenBuffer; public Connection(TapchatService service, MakeServerMessage message) throws Exception { mService = service; /* { "bid":-1, "eid":-1, "type":"makeserver", "time":-1, "highlight":false, "num_buffers":5, "cid":2135, "name":"IRCCloud", "nick":"fR", "nickserv_nick":"fR", "nickserv_pass":"", "realname":"Eric", "hostname":"irc.irccloud.com", "port":6667, "away":"", "disconnected":false, "away_timeout":0, "autoback":true, "ssl":false, "server_pass":"" } */ mId = message.cid; updateDetails(message); } public String getName() { return mName; } public String getDisplayName() { if (!TextUtils.isEmpty(mName)) { return mName; } else { return mHostName; } } public String getDisplayState(Context context) { switch (mState) { case STATE_DISCONNECTED: return context.getString(R.string.disconnected_format, mName); case Connection.STATE_CONNECTING: return context.getString(R.string.connecting_format, mName); case Connection.STATE_RETRYING: return context.getString(R.string.retrying_format, mName); case Connection.STATE_CONNECTED: return context.getString(R.string.connected_format, mName); } return null; } public long getId() { return mId; } public boolean isBacklog() { return mIsBacklog; } public String getNick() { return mNick; } public ConsoleBuffer getConsoleBuffer() { return mConsoleBuffer; } public List<Buffer> getBuffers() { synchronized (mBuffers) { return new ArrayList<Buffer>(mBuffers.values()); } } public int getBufferCount() { synchronized (mBuffers) { return mBuffers.size(); } } public Buffer getBuffer(long id) { synchronized (mBuffers) { if (mBuffers.containsKey(id)) return mBuffers.get(id); else if (mConsoleBuffer != null && mConsoleBuffer.getId() == id) return mConsoleBuffer; return null; } } public int getBufferIndex(long bufferId) { synchronized (mBuffers) { return getBuffers().indexOf(getBuffer(bufferId)); } } public Buffer findBuffer(String name) { synchronized (mBuffers) { for (Buffer buffer : mBuffers.values()) { if (buffer.getName().equalsIgnoreCase(name)) { return buffer; } } return null; } } public boolean isSSL() { return mSSL; } public String getPassword() { return mPassword; } public String getHostName() { return mHostName; } public String getRealName() { return mRealName; } public int getPort() { return mPort; } public int getState() { return mState; } public void join(String channelName, TapchatService.PostCallback callback) { ChannelBuffer channel = (ChannelBuffer) findBuffer(channelName); if (channel != null && channel.isJoined()) { startBufferActivity(channel); if (callback != null) { callback.run(null, null); } return; } JoinMessage message = new JoinMessage(); message.channel = channelName; post(message, callback); } public void part(String channelName, TapchatService.PostCallback callback) { PartMessage message = new PartMessage(); message.channel = channelName; post(message, callback); } public void say(String to, String text, TapchatService.PostCallback callback) { SayMessage message = new SayMessage(); message.to = to; message.msg = text; post(message, callback); } public void acceptCert(String fingerprint, boolean accept) { AcceptCertMessage message = new AcceptCertMessage(); message.fingerprint = fingerprint; message.accept = accept; post(message, null); } public void openBuffer(String nick) { Buffer buffer = findBuffer(nick); if (buffer == null) { say(nick, null, null); } else { buffer.unarchive(); startBufferActivity(buffer); } } public void reconnect() { post(new ReconnectMessage(), null); } public void disconnect() { post(new DisconnectMessage(), null); } public void delete() { post(new DeleteConnectionMessage(), null); } public void edit(String name, String hostname, String nickname, String port, String realname, boolean useSSL, String password, TapchatService.PostCallback callback) { EditServerMessage message = new EditServerMessage(); message.name = name; message.hostname = hostname; message.nickname = nickname; message.port = port; message.realname = realname; message.ssl = useSSL ? "1" : "0"; message.server_pass = password; // FIXME: Not implemented yet // message.channels = ""; // message.nspass = ""; post(message, callback); } TapchatService getService() { return mService; } public synchronized void processMessage(Message message) throws Exception { String type = message.type; if (mMessageHandlers.containsKey(type)) { mMessageHandlers.get(type).handleMessage(message); } boolean isBacklogMessage = message.is_backlog; if ((!isBacklogMessage) && (!mIsBacklog) && mInitializedMessageHandlers.containsKey(type)) { mInitializedMessageHandlers.get(type).handleMessage(message); } if (message.bid != null && !message.type.equals(MakeBufferMessage.TYPE)) { Buffer buffer = getBuffer(message.bid); if (buffer != null) { buffer.processMessage((BufferEventMessage) message); } } } public void handleResponse(ResponseMessage response, Message request) { String type = response.type; if (type != null && type.equals("open_buffer")) { String bufferName = ((OpenBufferMessage) response.msg).name; Buffer buffer = findBuffer(bufferName); if (buffer == null) { mPendingOpenBuffer = bufferName; } else { startBufferActivity(buffer); mPendingOpenBuffer = null; } } } void notifyChanged() { mService.postToBus(new ConnectionChangedEvent(this)); } public void reload(MakeServerMessage message) throws JSONException { updateDetails(message); // FIXME: Notify all buffers that we're reloading? } public void post(Message message, TapchatService.PostCallback callback) { message.cid = getId(); mService.post(message, callback); } private void startBufferActivity(Buffer buffer) { Context appContext = TapchatApp.get(); Intent intent = new Intent(TapchatApp.ACTION_OPEN_BUFFER); intent.putExtra("cid", String.valueOf(buffer.getConnection().getId())); intent.putExtra("bid", String.valueOf(buffer.getId())); appContext.sendOrderedBroadcast(intent, null); } @Override public String toString() { return String.format("Connection{id=%s, name=%s}", getId(), getName()); } private Map<String, MessageHandler> mMessageHandlers = ImmutableMap.<String, MessageHandler>builder() .put(EndOfBacklogMessage.TYPE, new MessageHandler<EndOfBacklogMessage>() { @Override public void handleMessage(EndOfBacklogMessage message) throws Exception { mIsBacklog = false; mService.updateLoadingProgress(); synchronized (mBuffers) { for (Buffer buffer : mBuffers.values()) { if (!buffer.exists()) { removeBuffer(buffer); } } } } }).put(MakeBufferMessage.TYPE, new MessageHandler<MakeBufferMessage>() { @Override public void handleMessage(MakeBufferMessage message) throws Exception { long bid = message.bid; Buffer buffer = getBuffer(bid); if (buffer != null) { buffer.reload(message); return; } String bufferType = message.buffer_type; switch (bufferType) { case "channel": buffer = new ChannelBuffer(Connection.this, message); break; case "conversation": buffer = new ConversationBuffer(Connection.this, message); break; case "console": buffer = new ConsoleBuffer(Connection.this, message); mConsoleBuffer = (ConsoleBuffer) buffer; return; default: throw new Exception("Unknown buffer type: " + bufferType); } synchronized (mBuffers) { mBuffers.put(bid, buffer); } final Buffer theBuffer = buffer; mService.postToBus(new BufferAddedEvent(theBuffer)); if (mPendingOpenBuffer != null && mPendingOpenBuffer.equals(buffer.getName())) { startBufferActivity(buffer); mPendingOpenBuffer = null; } } }).build(); private Map<String, MessageHandler> mInitializedMessageHandlers = new ImmutableMap.Builder<String, MessageHandler>() .put(ServerDetailsChangedMessage.TYPE, new MessageHandler<ServerDetailsChangedMessage>() { @Override public void handleMessage(ServerDetailsChangedMessage message) throws Exception { updateDetails(message); } }).put(YouNickchangeMessage.TYPE, new MessageHandler<YouNickchangeMessage>() { @Override public void handleMessage(YouNickchangeMessage message) throws Exception { mNick = message.newnick; notifyChanged(); } }).put(ConnectingMessage.TYPE, new MessageHandler<ConnectingMessage>() { @Override public void handleMessage(ConnectingMessage message) throws Exception { mNick = message.nick; mState = STATE_CONNECTING; notifyChanged(); } }).put(ConnectingRetryMessage.TYPE, new MessageHandler<ConnectingRetryMessage>() { @Override public void handleMessage(ConnectingRetryMessage message) throws Exception { mState = STATE_RETRYING; notifyChanged(); } }).put(WaitingToRetryMessage.TYPE, new MessageHandler<WaitingToRetryMessage>() { @Override public void handleMessage(WaitingToRetryMessage message) throws Exception { mState = STATE_RETRYING; notifyChanged(); } }).put(ConnectingCancelledMessage.TYPE, new MessageHandler<ConnectingCancelledMessage>() { @Override public void handleMessage(ConnectingCancelledMessage message) throws Exception { mState = STATE_DISCONNECTED; notifyChanged(); } }).put(ConnectingFailedMessage.TYPE, new MessageHandler<ConnectingFailedMessage>() { @Override public void handleMessage(ConnectingFailedMessage message) throws Exception { mState = STATE_DISCONNECTED; notifyChanged(); } }).put(ConnectedMessage.TYPE, new MessageHandler<ConnectedMessage>() { @Override public void handleMessage(ConnectedMessage message) throws Exception { // nop, just means the socket is established, wait for connecting_finished } }).put(ConnectingFinishedMessage.TYPE, new MessageHandler<ConnectingFinishedMessage>() { @Override public void handleMessage(ConnectingFinishedMessage message) throws Exception { mState = STATE_CONNECTED; notifyChanged(); } }).put(SocketClosedMessage.TYPE, new MessageHandler<SocketClosedMessage>() { @Override public void handleMessage(SocketClosedMessage message) throws Exception { mState = STATE_DISCONNECTED; notifyChanged(); } }).put(InvalidCertMessage.TYPE, new MessageHandler<InvalidCertMessage>() { @Override public void handleMessage(InvalidCertMessage message) throws Exception { Context appContext = TapchatApp.get(); Intent intent = new Intent(TapchatApp.ACTION_INVALID_CERT); intent.putExtra(InvalidConnectionCertActivity.EXTRA_CID, message.cid); intent.putExtra(InvalidConnectionCertActivity.EXTRA_HOSTNAME, message.hostname); intent.putExtra(InvalidConnectionCertActivity.EXTRA_FINGERPRINT, message.fingerprint); intent.putExtra(InvalidConnectionCertActivity.EXTRA_ERROR, message.error); appContext.sendBroadcast(intent, null); } }).put(DeleteBufferMessage.TYPE, new MessageHandler<DeleteBufferMessage>() { @Override public void handleMessage(DeleteBufferMessage message) throws Exception { Buffer buffer = getBuffer(message.bid); removeBuffer(buffer); } }).put(BufferArchivedMessage.TYPE, new MessageHandler<BufferArchivedMessage>() { @Override public void handleMessage(BufferArchivedMessage message) throws Exception { Buffer buffer = getBuffer(message.bid); buffer.setArchived(true); mService.postToBus(new BufferChangedEvent(buffer)); } }).put(BufferUnarchivedMessage.TYPE, new MessageHandler<BufferUnarchivedMessage>() { @Override public void handleMessage(BufferUnarchivedMessage message) throws Exception { long bid = message.bid; Buffer buffer = getBuffer(bid); buffer.setArchived(false); mService.postToBus(new BufferChangedEvent(buffer)); } }).build(); private void removeBuffer(Buffer buffer) { buffer.notifyRemoved(); synchronized (mBuffers) { mBuffers.remove(buffer.getId()); } mService.postToBus(new BufferRemovedEvent(buffer)); } private void updateDetails(ServerDetailsChangedMessage message) throws JSONException { mExists = true; mName = message.name; mNick = message.nick; mRealName = message.realname; mHostName = message.hostname; mPort = message.port; mSSL = message.ssl; mPassword = message.server_pass; mIsBacklog = (mService.getConnectionState() != TapchatService.STATE_LOADED); if (message.disconnected) { // FIXME: What if the state should be "connecting"? mState = STATE_DISCONNECTED; } else { mState = STATE_CONNECTED; } notifyChanged(); } public void serviceDisconnected() { mExists = false; synchronized (mBuffers) { for (Buffer buffer : mBuffers.values()) { buffer.serviceDisconnected(); } } } public boolean exists() { return mExists; } }