package com.limegroup.gnutella.gui.connection;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;
import com.limegroup.gnutella.ManagedConnection;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.gui.GUIUtils;
import com.limegroup.gnutella.gui.tables.AbstractDataLine;
import com.limegroup.gnutella.gui.tables.LimeTableColumn;
import com.limegroup.gnutella.gui.tables.TimeRemainingHolder;
public final class ConnectionDataLine extends AbstractDataLine {
/**
* Host column info
*/
static final int HOST_IDX = 0;
private static final LimeTableColumn HOST_COLUMN =
new LimeTableColumn(HOST_IDX, "CV_COLUMN_HOST",
218, true, String.class);
/**
* Status column info
*/
static final int STATUS_IDX = 1;
private static final LimeTableColumn STATUS_COLUMN =
new LimeTableColumn(STATUS_IDX, "CV_COLUMN_STATUS",
70, true, String.class);
/**
* Messages column info
*/
static final int MESSAGES_IDX = 2;
private static final LimeTableColumn MESSAGES_COLUMN =
new LimeTableColumn(MESSAGES_IDX, "CV_COLUMN_MESSAGE",
97, true, MessagesHolder.class);
/**
* Bandwidth column info
*/
static final int BANDWIDTH_IDX = 3;
private static final LimeTableColumn BANDWIDTH_COLUMN =
new LimeTableColumn(BANDWIDTH_IDX, "CV_COLUMN_BANDWIDTH",
115, true, BandwidthHolder.class);
/**
* Dropped column info
*/
static final int DROPPED_IDX = 4;
private static final LimeTableColumn DROPPED_COLUMN =
new LimeTableColumn(DROPPED_IDX, "CV_COLUMN_DROPPED",
92, true, DroppedHolder.class);
/**
* Protocol column info
*/
static final int PROTOCOL_IDX = 5;
private static final LimeTableColumn PROTOCOL_COLUMN =
new LimeTableColumn(PROTOCOL_IDX, "CV_COLUMN_PROTOCOL",
60, true, ProtocolHolder.class);
/**
* Vendor column info
*/
static final int VENDOR_IDX = 6;
private static final LimeTableColumn VENDOR_COLUMN =
new LimeTableColumn(VENDOR_IDX, "CV_COLUMN_VENDOR",
116, true, String.class);
/**
* Time connected info
*/
static final int TIME_IDX = 7;
private static final LimeTableColumn TIME_COLUMN =
new LimeTableColumn(TIME_IDX, "CV_COLUMN_TIME",
44, true, TimeRemainingHolder.class);
/**
* The compression saved statistics.
*/
static final int COMPRESSION_IDX = 8;
private static final LimeTableColumn COMPRESS_COLUMN =
new LimeTableColumn(COMPRESSION_IDX, "CV_COLUMN_COMPRESSION",
114, false, DroppedHolder.class);
/**
* The value for the percent full in the QRP table.
*/
static final int QRP_FULL_IDX = 9;
private static final LimeTableColumn QRP_FULL_COLUMN =
new LimeTableColumn(QRP_FULL_IDX, "CV_COLUMN_QRP_FULL",
70, false, QRPHolder.class);
static final int QRP_USED_IDX = 10;
private static final LimeTableColumn QRP_USED_COLUMN =
new LimeTableColumn(QRP_USED_IDX, "CV_COLUMN_QRP_USED",
70, false, String.class);
/**
* Total number of columns
*/
static final int NUMBER_OF_COLUMNS = 11;
/**
* The main connection this dataline is based on
*/
private ManagedConnection MCONNECTION;
/**
* String for connecting status
*/
private static final String CONNECTING_STRING =
GUIMediator.getStringResource("CV_TABLE_STRING_CONNECTING");
/**
* String for outgoing status
*/
private static final String OUTGOING_STRING =
GUIMediator.getStringResource("CV_TABLE_STRING_OUTGOING");
/**
* String for incoming status
*/
private static final String INCOMING_STRING =
GUIMediator.getStringResource("CV_TABLE_STRING_INCOMING");
/**
* String for 'Connected on' tooltip
*/
private static final String CONNECTED_ON =
GUIMediator.getStringResource("CV_TABLE_STRING_CONNECTED_ON");
/**
* Cached host
*/
private volatile String _host;
/**
* Cached status
*/
private String _status;
/**
* Time this connected or initialized
*/
private long _time;
/**
* Whether or not this dataline is in the 'connecting' state
*/
private boolean _isConnecting = true;
/**
* Variable for whether or not the host name has been resolved for
* this connection.
*/
private boolean _hasResolvedAddress = false;
/**
* Boolean for whether or not the 'host' of a line has changed.
*/
private static volatile boolean _hostChanged = false;
/**
* Boolean for whether a line has updated from connecting to connected
*/
private static boolean _updated = false;
/**
* Number of columns
*/
public int getColumnCount() { return NUMBER_OF_COLUMNS; }
/**
* Sets up the dataline for use with the connection
*/
public void initialize(Object conn) {
super.initialize(conn);
MCONNECTION = (ManagedConnection)conn;
_host = MCONNECTION.getAddress();
_status = CONNECTING_STRING;
_time = System.currentTimeMillis();
}
/**
* Returns the value for the specified index.
*/
public Object getValueAt(int idx) {
switch(idx) {
case HOST_IDX:
if(!_hasResolvedAddress // hasn't yet resolved address
&& !_isConnecting // must be connected
&& (System.currentTimeMillis() - _time) > 10000)
assignHostName();
return _host;
case STATUS_IDX: return _status;
case MESSAGES_IDX:
if (_isConnecting) return null;
return new MessagesHolder(
MCONNECTION.getNumMessagesReceived(),
MCONNECTION.getNumMessagesSent()
);
case BANDWIDTH_IDX:
if (_isConnecting) return null;
return new BandwidthHolder(
MCONNECTION.getMeasuredDownstreamBandwidth(),
MCONNECTION.getMeasuredUpstreamBandwidth()
);
case DROPPED_IDX:
if (_isConnecting) return null;
// NOTE: this use to be getPercent[Sent|Received]Dropped
// However that had the side-effect of altering the
// connection's stats.
// This provides more accurate statistics anyway,
// rather than a snapshot-erase-style number.
return new DroppedHolder(
(float)MCONNECTION.getNumReceivedMessagesDropped() /
( (float)MCONNECTION.getNumMessagesReceived() + 1.0f ),
(float)MCONNECTION.getNumSentMessagesDropped() /
( (float)MCONNECTION.getNumMessagesSent() + 1.0f )
);
case PROTOCOL_IDX: return new ProtocolHolder( MCONNECTION );
case VENDOR_IDX:
if (_isConnecting) return null;
String vendor = MCONNECTION.getUserAgent();
return vendor == null ? "" : vendor;
case TIME_IDX:
return new TimeRemainingHolder( (int)(
(System.currentTimeMillis() - _time) / 1000) );
case COMPRESSION_IDX:
if (_isConnecting) return null;
return new DroppedHolder(
MCONNECTION.getReadSavedFromCompression(),
MCONNECTION.getSentSavedFromCompression() );
case QRP_FULL_IDX:
if(_isConnecting) return null;
return new QRPHolder(
MCONNECTION.getQueryRouteTablePercentFull(),
MCONNECTION.getQueryRouteTableSize());
case QRP_USED_IDX:
if(_isConnecting) return null;
int empty = MCONNECTION.getQueryRouteTableEmptyUnits();
int inuse = MCONNECTION.getQueryRouteTableUnitsInUse();
if(empty == -1 || inuse == -1)
return null;
else
return empty + " / " + inuse;
}
return null;
}
/**
* Helper method that launches a separate thread to look up the host name
* of the given connection. The thread is necessary because the lookup
* can take considerable time.
*/
private void assignHostName() {
// put this outside of the runnable so multiple attempts aren't done.
_hasResolvedAddress = true;
GUIMediator.instance().schedule(new HostAssigner(this));
}
/**
* Return the table column for this index.
*/
public LimeTableColumn getColumn(int idx) {
switch(idx) {
case HOST_IDX: return HOST_COLUMN;
case STATUS_IDX: return STATUS_COLUMN;
case MESSAGES_IDX: return MESSAGES_COLUMN;
case BANDWIDTH_IDX: return BANDWIDTH_COLUMN;
case DROPPED_IDX: return DROPPED_COLUMN;
case PROTOCOL_IDX: return PROTOCOL_COLUMN;
case VENDOR_IDX: return VENDOR_COLUMN;
case TIME_IDX: return TIME_COLUMN;
case COMPRESSION_IDX: return COMPRESS_COLUMN;
case QRP_FULL_IDX: return QRP_FULL_COLUMN;
case QRP_USED_IDX: return QRP_USED_COLUMN;
}
return null;
}
public boolean isClippable(int idx) {
return true;
}
public int getTypeAheadColumn() {
return HOST_IDX;
}
public boolean isDynamic(int idx) {
switch(idx) {
case MESSAGES_IDX:
case BANDWIDTH_IDX:
case DROPPED_IDX:
case COMPRESSION_IDX:
case QRP_FULL_IDX:
case QRP_USED_IDX:
return true;
case HOST_IDX:
// if a host changed, set it to false for the future
// and return true. otherwise return false.
if ( _hostChanged ) {
_hostChanged = false;
return true;
} else {
return false;
}
case VENDOR_IDX:
case STATUS_IDX:
case PROTOCOL_IDX:
if ( _updated ) {
_updated = false;
return true;
} else {
return false;
}
}
return false;
}
boolean isPeer() {
return MCONNECTION.isSupernodeSupernodeConnection();
}
boolean isUltrapeer() {
return MCONNECTION.isClientSupernodeConnection();
}
boolean isLeaf() {
return MCONNECTION.isSupernodeClientConnection();
}
boolean isConnecting() {
return _isConnecting;
}
/**
* Updates this connection from a 'connecting' to a 'connected' state.
*/
public void update() {
_isConnecting = false;
boolean isOutgoing = MCONNECTION.isOutgoing();
_status = isOutgoing ? OUTGOING_STRING : INCOMING_STRING;
_host = MCONNECTION.getInetAddress().getHostAddress();
// once it's connected, add it to the dictionary for host entry
if ( isOutgoing )
ConnectionMediator.instance().addKnownHost(
_host, MCONNECTION.getPort()
);
_updated = true;
_time = MCONNECTION.getConnectionTime();
}
/**
* Returns whether or not this line is connected.
*/
public boolean isConnected() {
return !_isConnecting;
}
/**
* Returns the ToolTip text for this DataLine.
* Display some of the finer connection information.
*/
public String[] getToolTipArray(int col) {
Properties p = MCONNECTION.headers().props();
ArrayList tips = new ArrayList();
if ( p == null ) {
// for the lazy .4 connections (yes, some are still there)
tips.add(CONNECTED_ON + " " + GUIUtils.msec2DateTime(_time));
} else {
tips.add(CONNECTED_ON + " " + GUIUtils.msec2DateTime(_time));
tips.add("");
String k;
Enumeration ps = p.propertyNames();
while(ps.hasMoreElements()) {
k = (String)ps.nextElement();
tips.add(k + ": " + p.getProperty(k));
}
}
return (String[])tips.toArray(new String[0]);
}
/**
* Assigns the host field to the line without holding an explicit
* reference to it.
*/
private static class HostAssigner implements Runnable {
private final WeakReference line;
HostAssigner(ConnectionDataLine cdl) {
line = new WeakReference(cdl);
}
public void run() {
ConnectionDataLine cdl = (ConnectionDataLine)line.get();
if(cdl != null) {
try {
cdl._host = InetAddress.getByName(cdl._host).getHostName();
ConnectionDataLine._hostChanged = true;
} catch (UnknownHostException ignored) {}
}
}
}
}