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); } } }