package org.limewire.ui.swing.advanced.connection;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import javax.swing.table.TableColumn;
import org.limewire.core.api.connection.ConnectionItem;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.util.BackgroundExecutorService;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
import org.limewire.util.CommonUtils;
import ca.odell.glazedlists.gui.TableFormat;
/**
* This defines the table format for the Connection table. For formatting
* individual connection items, ConnectionTableFormat uses logic that was
* originally contained in the ConnectionDataLine class.
*/
public class ConnectionTableFormat implements TableFormat<ConnectionItem> {
/** String for connecting status */
private static final String CONNECTING_STRING = I18n.tr("Connecting...");
/** String for outgoing status */
private static final String OUTGOING_STRING = I18n.tr("Outgoing");
/** String for incoming status */
private static final String INCOMING_STRING = I18n.tr("Incoming");
/** String for 'Connected on' tooltip */
private static final String CONNECTED_ON = I18n.tr("Connected on");
private static final String LEAF = I18n.tr("Leaf");
private static final String ULTRAPEER = I18n.tr("Ultrapeer");
private static final String PEER = I18n.tr("Peer");
private static final String STANDARD = I18n.tr("Standard");
public static final int HOST_IDX = 0;
public static final int STATUS_IDX = 1;
public static final int MESSAGES_IDX = 2;
public static final int MESSAGES_IN_IDX = 3;
public static final int MESSAGES_OUT_IDX = 4;
public static final int BANDWIDTH_IDX = 5;
public static final int BANDWIDTH_IN_IDX = 6;
public static final int BANDWIDTH_OUT_IDX = 7;
public static final int DROPPED_IDX = 8;
public static final int DROPPED_IN_IDX = 9;
public static final int DROPPED_OUT_IDX = 10;
public static final int PROTOCOL_IDX = 11;
public static final int VENDOR_IDX = 12;
public static final int TIME_IDX = 13;
public static final int COMPRESSION_IDX = 14;
public static final int COMPRESSION_IN_IDX = 15;
public static final int COMPRESSION_OUT_IDX = 16;
public static final int SSL_IDX = 17;
public static final int SSL_IN_IDX = 18;
public static final int SSL_OUT_IDX = 19;
public static final int QRP_FULL_IDX = 20;
public static final int QRP_USED_IDX = 21;
private final ConnectionColumn[] columns = {
new ConnectionColumn(HOST_IDX, "CV_COLUMN_HOST",
I18n.tr("Host"), 218, true, String.class),
new ConnectionColumn(STATUS_IDX, "CV_COLUMN_STATUS",
I18n.tr("Status"), 70, true, String.class),
new ConnectionColumn(MESSAGES_IDX, "CV_COLUMN_MESSAGE",
I18n.tr("Messages (I/O)"), 97, true, MessagesValue.class),
new ConnectionColumn(MESSAGES_IN_IDX, "CV_COLUMN_MESSAGE_IN",
I18n.tr("Messages In"), 97, false, Integer.class),
new ConnectionColumn(MESSAGES_OUT_IDX, "CV_COLUMN_MESSAGE_OUT",
I18n.tr("Messages Out"), 97, false, Integer.class),
new ConnectionColumn(BANDWIDTH_IDX, "CV_COLUMN_BANDWIDTH",
I18n.tr("Bandwidth (I/O)"), 115, true, BandwidthValue.class),
new ConnectionColumn(BANDWIDTH_IN_IDX, "CV_COLUMN_BANDWIDTH_IN",
I18n.tr("Bandwidth In"), 115, false, BandwidthValue.class),
new ConnectionColumn(BANDWIDTH_OUT_IDX, "CV_COLUMN_BANDWIDTH_OUT",
I18n.tr("Bandwidth Out"), 115, false, BandwidthValue.class),
new ConnectionColumn(DROPPED_IDX, "CV_COLUMN_DROPPED",
I18n.tr("Dropped (I/O)"), 92, true, DroppedValue.class),
new ConnectionColumn(DROPPED_IN_IDX, "CV_COLUMN_DROPPED_IN",
I18n.tr("Dropped In"), 92, false, DroppedValue.class),
new ConnectionColumn(DROPPED_OUT_IDX, "CV_COLUMN_DROPPED_OUT",
I18n.tr("Dropped Out"), 92, false, DroppedValue.class),
new ConnectionColumn(PROTOCOL_IDX, "CV_COLUMN_PROTOCOL",
I18n.tr("Protocol"), 60, true, ProtocolValue.class),
new ConnectionColumn(VENDOR_IDX, "CV_COLUMN_VENDOR",
I18n.tr("Vendor/Version"), 116, true, String.class),
new ConnectionColumn(TIME_IDX, "CV_COLUMN_TIME",
I18n.tr("Time"), 44, true, TimeRemainingValue.class),
new ConnectionColumn(COMPRESSION_IDX, "CV_COLUMN_COMPRESSION",
I18n.tr("Compressed (I/O)"), 114, false, DroppedValue.class),
new ConnectionColumn(COMPRESSION_IN_IDX, "CV_COLUMN_COMPRESSION_IN",
I18n.tr("Compressed In"), 114, false, DroppedValue.class),
new ConnectionColumn(COMPRESSION_OUT_IDX, "CV_COLUMN_COMPRESSION_OUT",
I18n.tr("Compressed Out"), 114, false, DroppedValue.class),
new ConnectionColumn(SSL_IDX, "CV_COLUMN_SSL",
I18n.tr("SSL Overhead (I/O)"), 100, false, DroppedValue.class),
new ConnectionColumn(SSL_IN_IDX, "CV_COLUMN_SSL_IN",
I18n.tr("SSL Overhead In"), 100, false, DroppedValue.class),
new ConnectionColumn(SSL_OUT_IDX, "CV_COLUMN_SSL_OUT",
I18n.tr("SSL Overhead Out"), 100, false, DroppedValue.class),
new ConnectionColumn(QRP_FULL_IDX, "CV_COLUMN_QRP_FULL",
I18n.tr("QRP (%)"), 70, false, QRPValue.class),
new ConnectionColumn(QRP_USED_IDX, "CV_COLUMN_QRP_USED",
I18n.tr("QRP Empty"), 70, false, String.class)
};
/**
* Returns the number of columns.
*/
@Override
public int getColumnCount() {
return columns.length;
}
/**
* Returns the name of the column at the specified index.
*/
@Override
public String getColumnName(int column) {
if (column < columns.length) {
return columns[column].getName();
}
return null;
}
/**
* Returns the column value for the specified connection item and column
* index.
*/
@Override
public Object getColumnValue(ConnectionItem baseObject, int column) {
if (column < columns.length) {
return getValueAt(baseObject, column);
}
return null;
}
/**
* Returns the table column for the specified ConnectionItem index.
*/
public ConnectionColumn getColumn(int index) {
return columns[index];
}
/**
* Generates status text for the specified connection.
*/
private String getStatusText(ConnectionItem connectionItem) {
switch (connectionItem.getStatus()) {
case OUTGOING:
return OUTGOING_STRING;
case INCOMING:
return INCOMING_STRING;
case CONNECTING:
default:
return CONNECTING_STRING;
}
}
/**
* Returns the tooltip text for the specified connection, which displays
* more fine-grained connection information.
*/
public String[] getToolTipArray(ConnectionItem connectionItem) {
Properties props = connectionItem.getHeaderProperties();
List<String> tips = new ArrayList<String>();
if (props == null) {
// for the lazy .4 connections (yes, some are still there)
tips.add(CONNECTED_ON + " " + GuiUtils.msec2DateTime(connectionItem.getTime()));
} else {
tips.add(CONNECTED_ON + " " + GuiUtils.msec2DateTime(connectionItem.getTime()));
tips.add("");
String k;
Enumeration ps = props.propertyNames();
while (ps.hasMoreElements()) {
k = (String) ps.nextElement();
tips.add(k + ": " + props.getProperty(k));
}
}
return tips.toArray(new String[tips.size()]);
}
/**
* Returns the value for the specified connection and index.
* @param connectionItem the ConnectionItem containing data
* @param index one of the index constants
*/
private Object getValueAt(ConnectionItem connectionItem, int index) {
switch (index) {
case HOST_IDX:
if (!connectionItem.isAddressResolved() // address not resolved yet
&& connectionItem.isConnected() // must be connected
&& System.currentTimeMillis() - connectionItem.getTime() > 10000
&& SwingUiSettings.RESOLVE_CONNECTION_HOSTNAMES.getValue()) {
assignHostName(connectionItem);
} else if(connectionItem.isAddressResolved()
&& !SwingUiSettings.RESOLVE_CONNECTION_HOSTNAMES.getValue()) {
connectionItem.resetHostName();
}
return connectionItem.getHostName() + ":" + connectionItem.getPort();
case STATUS_IDX:
return getStatusText(connectionItem);
case MESSAGES_IDX:
if (!connectionItem.isConnected()) return null;
return new MessagesValue(
connectionItem.getNumMessagesReceived(),
connectionItem.getNumMessagesSent()
);
case MESSAGES_IN_IDX:
if (!connectionItem.isConnected()) return null;
return connectionItem.getNumMessagesReceived();
case MESSAGES_OUT_IDX:
if (!connectionItem.isConnected()) return null;
return connectionItem.getNumMessagesSent();
case BANDWIDTH_IDX:
if (!connectionItem.isConnected()) return null;
return new BandwidthValue(
connectionItem.getMeasuredDownstreamBandwidth(),
connectionItem.getMeasuredUpstreamBandwidth()
);
case BANDWIDTH_IN_IDX:
if (!connectionItem.isConnected()) return null;
return new BandwidthValue(connectionItem.getMeasuredDownstreamBandwidth());
case BANDWIDTH_OUT_IDX:
if (!connectionItem.isConnected()) return null;
return new BandwidthValue(connectionItem.getMeasuredUpstreamBandwidth());
case DROPPED_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getNumReceivedMessagesDropped() /
(connectionItem.getNumMessagesReceived() + 1.0f),
connectionItem.getNumSentMessagesDropped() /
(connectionItem.getNumMessagesSent() + 1.0f)
);
case DROPPED_IN_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getNumReceivedMessagesDropped() /
(connectionItem.getNumMessagesReceived() + 1.0f)
);
case DROPPED_OUT_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getNumSentMessagesDropped() /
(connectionItem.getNumMessagesSent() + 1.0f)
);
case PROTOCOL_IDX:
return new ProtocolValue(connectionItem);
case VENDOR_IDX:
if (!connectionItem.isConnected()) return null;
String vendor = connectionItem.getUserAgent();
return (vendor == null) ? "" : vendor;
case TIME_IDX:
return new TimeRemainingValue((int)
((System.currentTimeMillis() - connectionItem.getTime()) / 1000));
case COMPRESSION_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getReadSavedFromCompression(),
connectionItem.getSentSavedFromCompression());
case COMPRESSION_IN_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getReadSavedFromCompression());
case COMPRESSION_OUT_IDX:
if (!connectionItem.isConnected()) return null;
return new DroppedValue(
connectionItem.getSentSavedFromCompression());
case SSL_IDX:
return new DroppedValue(
connectionItem.getReadLostFromSSL(),
connectionItem.getSentLostFromSSL());
case SSL_IN_IDX:
return new DroppedValue(
connectionItem.getReadLostFromSSL());
case SSL_OUT_IDX:
return new DroppedValue(
connectionItem.getSentLostFromSSL());
case QRP_FULL_IDX:
if (!connectionItem.isConnected()) return null;
return new QRPValue(
connectionItem.getQueryRouteTablePercentFull(),
connectionItem.getQueryRouteTableSize());
case QRP_USED_IDX:
if (!connectionItem.isConnected()) return null;
int empty = connectionItem.getQueryRouteTableEmptyUnits();
int inuse = connectionItem.getQueryRouteTableUnitsInUse();
if (empty == -1 || inuse == -1) {
return null;
} else {
return empty + " / " + inuse;
}
default:
return null;
}
}
/**
* Looks up the host name for the connection. This method launches a
* separate thread to perform the lookup because the task can take
* considerable time.
*/
private void assignHostName(ConnectionItem connectionItem) {
// Put this outside of the runnable so multiple attempts aren't done.
connectionItem.setAddressResolved(true);
// Start task to update host name.
BackgroundExecutorService.execute(new HostAssigner(connectionItem));
}
/**
* Defines a column in the Connection table.
*/
public static class ConnectionColumn extends TableColumn {
private final String id;
private final String name;
private final int width;
private final boolean visible;
private final Class<?> columnClass;
public ConnectionColumn(int modelIndex, String id, String name,
int width, boolean visible, Class<?> columnClass) {
super(modelIndex);
this.id = id;
this.name = name;
this.width = width;
this.visible = visible;
this.columnClass = columnClass;
}
public Class<?> getColumnClass() {
return this.columnClass;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public boolean isVisible() {
return visible;
}
@Override
public int getWidth() {
return width;
}
}
/**
* Assigns the host name field to the connection item without holding an
* explicit reference to it.
*/
private static class HostAssigner implements Runnable {
private final WeakReference<ConnectionItem> item;
HostAssigner(ConnectionItem connectionItem) {
item = new WeakReference<ConnectionItem>(connectionItem);
}
public void run() {
ConnectionItem connectionItem = item.get();
if (connectionItem != null) {
try {
connectionItem.setHostName(InetAddress.getByName(
connectionItem.getHostName()).getHostName());
} catch (UnknownHostException ignored) {}
}
}
}
/**
* Defines the value object for the Bandwidth I/O field.
*/
private static class BandwidthValue implements Comparable<BandwidthValue> {
/** Static number formatter. */
private final static NumberFormat formatter;
static {
formatter = NumberFormat.getNumberInstance();
formatter.setMinimumFractionDigits(3);
formatter.setMaximumFractionDigits(3);
}
private final float down;
private final float up;
private final String text;
/**
* Constructs a BandwidthValue with the specified upload value.
*/
public BandwidthValue(float up) {
this.up = up;
this.down = 0.0f;
this.text = formatter.format(up) + " " + GuiUtils.GENERAL_UNIT_KBPSEC;
}
/**
* Constructs a BandwidthValue with the specified download and upload
* values.
*/
public BandwidthValue(float down, float up) {
this.down = down;
this.up = up;
this.text = formatter.format(down) + " / " + formatter.format(up) +
GuiUtils.GENERAL_UNIT_KBPSEC;
}
/**
* Compares this object with the specified object by the sum of the
* up and down values.
*/
@Override
public int compareTo(BandwidthValue other) {
float me = down + up;
float you = other.down + other.up;
if ( me > you ) return 1;
if ( me < you ) return -1;
return 0;
}
@Override
public String toString() {
return text;
}
}
/**
* Defines the value object for the Dropped I/O field.
*/
private static class DroppedValue implements Comparable<DroppedValue> {
private final int in;
private final int out;
private final String text;
/**
* Constructs a DroppedValue with the specified input value.
*/
public DroppedValue(float in) {
this.in = Math.min(100, (int)(in * 100));
this.out = 0;
this.text = Integer.toString(this.in) + "%";
}
/**
* Constructs a DroppedValue with the specified input and output values.
*/
public DroppedValue(float in, float out) {
this.in = Math.min(100, (int)(in * 100));
this.out = Math.min(100, (int)(out * 100));
this.text = Integer.toString(this.in) + "% / " + Integer.toString(this.out) + "%";
}
/**
* Compares this object with the specified object by the sum of the
* input and output values.
*/
@Override
public int compareTo(DroppedValue other) {
return ((in + out) - (other.in + other.out));
}
@Override
public String toString() {
return text;
}
}
/**
* Defines the value object for the Messages I/O field.
*/
private static class MessagesValue implements Comparable<MessagesValue> {
private final int received;
private final int sent;
private final String text;
public MessagesValue(int received, int sent) {
this.received = received;
this.sent = sent;
this.text = received + " / " + sent;
}
/**
* Compares this object with the specified object by the sum of the
* messages received and sent.
*/
@Override
public int compareTo(MessagesValue other) {
return ((received + sent) - (other.received + other.sent));
}
@Override
public String toString() {
return text;
}
}
/**
* Defines the value object for the Protocol field. The weight value is
* calculated using rules from the old ProtocolHolder class.
*/
private static class ProtocolValue implements Comparable<ProtocolValue> {
private final int weight;
private final String text;
public ProtocolValue(ConnectionItem connectionItem) {
weight = getWeightHostInfo(connectionItem);
if (connectionItem.isLeaf()) {
text = LEAF;
} else if (connectionItem.isUltrapeer()) {
text = ULTRAPEER;
} else if (connectionItem.isPeer()) {
text = PEER;
} else {
text = STANDARD;
}
}
private static int getWeightHostInfo(ConnectionItem connectionItem) {
//Assign weight based on bandwidth:
//4. ultrapeer->ultrapeer
//3. old-fashioned (unrouted)
//2. ultrapeer->leaf
//1. leaf->ultrapeer
if (connectionItem.isUltrapeerConnection()) {
if (connectionItem.isUltrapeer()) {
return 1;
} else {
return 4;
}
} else if (connectionItem.isLeaf()) {
return 2;
}
return 3;
}
@Override
public int compareTo(ProtocolValue other) {
return weight - other.weight;
}
@Override
public String toString() {
return text;
}
}
/**
* Defines the value object for the Query Route fields.
*/
private static class QRPValue implements Comparable<QRPValue> {
/** Format for the double. */
private final static NumberFormat PERCENT_FORMAT;
static {
PERCENT_FORMAT = NumberFormat.getPercentInstance();
PERCENT_FORMAT.setMaximumFractionDigits(2);
PERCENT_FORMAT.setMinimumFractionDigits(0);
PERCENT_FORMAT.setGroupingUsed(false);
}
/** The percent full of this QRP table. */
private final float percentFull;
/** The size of this QRP table. */
private final int size;
/** String representation. */
private final String text;
public QRPValue(double percentFull, int size) {
this.percentFull = (float) percentFull;
this.size = size;
text = PERCENT_FORMAT.format(percentFull/100) + " / " +
GuiUtils.toKilobytes(size);
}
/**
* Compares this object with the specified object using the two
* component values.
*/
@Override
public int compareTo(QRPValue other) {
if (percentFull != other.percentFull)
return (percentFull < other.percentFull) ? -1 : 1;
if (size != other.size)
return (size < other.size) ? -1 : 1;
return 0;
}
@Override
public String toString() {
return text;
}
}
/**
* Defines the value object for time remaining.
*/
private static class TimeRemainingValue implements Comparable<TimeRemainingValue> {
private final int timeRemaining;
public TimeRemainingValue(int intValue) {
timeRemaining = intValue;
}
@Override
public int compareTo(TimeRemainingValue o) {
return o.timeRemaining - timeRemaining;
}
@Override
public String toString() {
return (timeRemaining == 0) ? "" : CommonUtils.seconds2time(timeRemaining);
}
}
}