Javadoc, imports, and (potentially) some indentation. Maybe it won't do anything.... who knows?
(shendaras)
This commit is contained in:
@ -1,14 +1,14 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define the configuration for testing against one particular peer as a client
|
||||
*
|
||||
@ -26,17 +26,63 @@ public class ClientConfig {
|
||||
private String _comment;
|
||||
private int _averagePeriods[];
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PREFIX = "peer.";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PEER = ".peer";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFILE = ".statFile";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATDURATION = ".statDuration";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFREQUENCY = ".statFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDFREQUENCY = ".sendFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDSIZE = ".sendSize";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_COMMENT = ".comment";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_AVERAGEPERIODS = ".averagePeriods";
|
||||
|
||||
/**
|
||||
* Default constructor...
|
||||
*/
|
||||
public ClientConfig() {
|
||||
this(null, null, null, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
@ -65,64 +111,135 @@ public class ClientConfig {
|
||||
_averagePeriods = averagePeriods;
|
||||
}
|
||||
|
||||
/** peer to test against */
|
||||
/** Retrieves the peer to test against
|
||||
* @return the Destination (peer)
|
||||
*/
|
||||
public Destination getPeer() { return _peer; }
|
||||
|
||||
/**
|
||||
* Sets the peer to test against
|
||||
* @param peer the Destination (peer)
|
||||
*/
|
||||
public void setPeer(Destination peer) { _peer = peer; }
|
||||
|
||||
/** who we are when we test */
|
||||
|
||||
/**
|
||||
* Retrieves who we are when we test
|
||||
* @return the Destination (us)
|
||||
*/
|
||||
public Destination getUs() { return _us; }
|
||||
|
||||
/**
|
||||
* Sets who we are when we test
|
||||
* @param us the Destination (us)
|
||||
*/
|
||||
public void setUs(Destination us) { _us = us; }
|
||||
|
||||
/** location to write the current stats to */
|
||||
/**
|
||||
* Retrieves the location to write the current stats to
|
||||
* @return the name of the file
|
||||
*/
|
||||
public String getStatFile() { return _statFile; }
|
||||
|
||||
/**
|
||||
* Sets the name of the location we write the current stats to
|
||||
* @param statFile the name of the file
|
||||
*/
|
||||
public void setStatFile(String statFile) { _statFile = statFile; }
|
||||
|
||||
/** how many minutes of statistics should be maintained within the window for this client? */
|
||||
/**
|
||||
* Retrieves how many minutes of statistics should be maintained within the window for this client
|
||||
* @return the number of minutes
|
||||
*/
|
||||
public int getStatDuration() { return _statDuration; }
|
||||
|
||||
/**
|
||||
* Sets how many minutes of statistics should be maintained within the window for this client
|
||||
* @param durationMinutes the number of minutes
|
||||
*/
|
||||
public void setStatDuration(int durationMinutes) { _statDuration = durationMinutes; }
|
||||
|
||||
/** how frequenty the stats are written out (in seconds) */
|
||||
/**
|
||||
* Retrieves how frequently the stats are written out (in seconds)
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getStatFrequency() { return _statFrequency; }
|
||||
|
||||
/**
|
||||
* Sets how frequently the stats are written out (in seconds)
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setStatFrequency(int freqSeconds) { _statFrequency = freqSeconds; }
|
||||
|
||||
/** how frequenty we send messages to the peer (in seconds) */
|
||||
/**
|
||||
* Retrieves how frequenty we send messages to the peer (in seconds)
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getSendFrequency() { return _sendFrequency; }
|
||||
|
||||
/**
|
||||
* Sets how frequenty we send messages to the peer (in seconds)
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setSendFrequency(int freqSeconds) { _sendFrequency = freqSeconds; }
|
||||
|
||||
/**
|
||||
* How many bytes should the ping messages be (min values ~700, max ~32KB)?
|
||||
*
|
||||
* Retrieves how many bytes the ping messages should be
|
||||
* (min values ~700, max ~32KB)
|
||||
* @return the size in bytes
|
||||
*/
|
||||
public int getSendSize() { return _sendSize; }
|
||||
|
||||
/**
|
||||
* Sets how many bytes the ping messages should be
|
||||
* (min values ~700, max ~32KB)
|
||||
* @param numBytes the size in bytes
|
||||
*/
|
||||
public void setSendSize(int numBytes) { _sendSize = numBytes; }
|
||||
|
||||
/**
|
||||
* Brief 1 line description of the test. Useful comments are along the lines
|
||||
* of "The peer is located on a fast router and connection with 2 hop tunnels".
|
||||
*
|
||||
* Retrieves the brief, 1 line description of the test.
|
||||
* Useful comments are along the lines of "The peer is located on a fast router and connection with 2 hop tunnels".
|
||||
*
|
||||
* @return the brief comment
|
||||
*/
|
||||
public String getComment() { return _comment; }
|
||||
/**
|
||||
* Sets a brief, 1 line description (comment) of the test.
|
||||
* @param comment the brief comment
|
||||
*/
|
||||
public void setComment(String comment) { _comment = comment; }
|
||||
|
||||
/**
|
||||
* Periods that the client's tests should be averaged over.
|
||||
*
|
||||
* Retrieves the periods that the client's tests should be averaged over.
|
||||
* @return list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public int[] getAveragePeriods() { return _averagePeriods; }
|
||||
|
||||
/**
|
||||
* Sets the periods that the client's tests should be averaged over.
|
||||
* @param periods the list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public void setAveragePeriods(int periods[]) { _averagePeriods = periods; }
|
||||
|
||||
/**
|
||||
* How many hops is this test engine configured to use for its outbound and inbound tunnels?
|
||||
*
|
||||
* Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() { return _numHops; }
|
||||
|
||||
/**
|
||||
* Sets how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
* @param numHops the number of hops
|
||||
*/
|
||||
public void setNumHops(int numHops) { _numHops = numHops; }
|
||||
|
||||
/**
|
||||
* Load the client config from the properties specified, deriving the current
|
||||
* config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to load from
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was loaded correctly, false if there were errors
|
||||
*/
|
||||
public boolean load(Properties clientConfig, int peerNum) {
|
||||
@ -138,8 +255,9 @@ public class ClientConfig {
|
||||
|
||||
if ( (peerVal == null) || (statFileVal == null) || (statDurationVal == null) ||
|
||||
(statFrequencyVal == null) || (sendFrequencyVal == null) || (sendSizeVal == null) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer number "+ peerNum + " does not exist");
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Peer number "+ peerNum + " does not exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -151,25 +269,29 @@ public class ClientConfig {
|
||||
int sendSize = getInt(sendSizeVal);
|
||||
|
||||
if ( (duration <= 0) || (statFreq <= 0) || (sendFreq <= 0) || (sendSize <= 0) ) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid client config: duration [" + statDurationVal + "] stat frequency [" + statFrequencyVal +
|
||||
"] send frequency [" + sendFrequencyVal + "] send size [" + sendSizeVal + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
statFileVal = statFileVal.trim();
|
||||
if (statFileVal.length() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Stat file is blank for peer " + peerNum);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(peerVal);
|
||||
|
||||
if (commentVal == null)
|
||||
commentVal = "";
|
||||
commentVal = commentVal.trim();
|
||||
if (commentVal == null) {
|
||||
commentVal = "";
|
||||
}
|
||||
|
||||
commentVal = commentVal.trim();
|
||||
commentVal = commentVal.replace('\n', '_');
|
||||
|
||||
List periods = new ArrayList(4);
|
||||
@ -178,13 +300,15 @@ public class ClientConfig {
|
||||
while (tok.hasMoreTokens()) {
|
||||
String periodVal = tok.nextToken();
|
||||
int minutes = getInt(periodVal);
|
||||
if (minutes > 0)
|
||||
periods.add(new Integer(minutes));
|
||||
if (minutes > 0) {
|
||||
periods.add(new Integer(minutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
int avgPeriods[] = new int[periods.size()];
|
||||
for (int i = 0; i < periods.size(); i++)
|
||||
avgPeriods[i] = ((Integer)periods.get(i)).intValue();
|
||||
for (int i = 0; i < periods.size(); i++) {
|
||||
avgPeriods[i] = ((Integer)periods.get(i)).intValue();
|
||||
}
|
||||
|
||||
_comment = commentVal;
|
||||
_statDuration = duration;
|
||||
@ -205,6 +329,8 @@ public class ClientConfig {
|
||||
* Store the client config to the properties specified, deriving the current
|
||||
* config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to store to
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was stored correctly, false if there were errors
|
||||
*/
|
||||
public boolean store(Properties clientConfig, int peerNum) {
|
||||
@ -214,9 +340,11 @@ public class ClientConfig {
|
||||
}
|
||||
|
||||
String comment = _comment;
|
||||
if (comment == null)
|
||||
if (comment == null) {
|
||||
comment = "";
|
||||
comment = comment.trim();
|
||||
}
|
||||
|
||||
comment = comment.trim();
|
||||
comment = comment.replace('\n', '_');
|
||||
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
@ -243,8 +371,9 @@ public class ClientConfig {
|
||||
int i = Integer.parseInt(val);
|
||||
return i;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Value [" + val + "] is not a valid integer");
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Value [" + val + "] is not a valid integer");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
@ -13,7 +13,7 @@ import net.i2p.util.I2PThread;
|
||||
*/
|
||||
class ClientEngine {
|
||||
private static final Log _log = new Log(ClientEngine.class);
|
||||
/** who can send our pings/ */
|
||||
/** who can send our pings? */
|
||||
private Heartbeat _heartbeat;
|
||||
/** actual test state */
|
||||
private PeerData _data;
|
||||
@ -28,7 +28,8 @@ class ClientEngine {
|
||||
/**
|
||||
* Create a new engine that will send its pings through the given heartbeat
|
||||
* system, and will coordinate the test according to the configuration specified.
|
||||
*
|
||||
* @param heartbeat the Heartbeat to send pings through
|
||||
* @param config the Configuration to load configuration from =p
|
||||
*/
|
||||
public ClientEngine(Heartbeat heartbeat, ClientConfig config) {
|
||||
_heartbeat = heartbeat;
|
||||
@ -50,10 +51,18 @@ class ClientEngine {
|
||||
t.setName("HeartbeatClient " + _id);
|
||||
t.start();
|
||||
}
|
||||
/** who are we testing? */
|
||||
/**
|
||||
* Who are we testing?
|
||||
* @return the Destination (peer) we're testing
|
||||
*/
|
||||
public Destination getPeer() { return _data.getConfig().getPeer(); }
|
||||
/** what is our series identifier (used to locally identify a test) */
|
||||
|
||||
/**
|
||||
* What is our series identifier (used to locally identify a test)
|
||||
* @return the series identifier
|
||||
*/
|
||||
public int getSeriesNum() { return _id; }
|
||||
|
||||
/**
|
||||
* receive notification from the heartbeat system that a pong was received in
|
||||
* reply to a ping we have sent.
|
||||
@ -74,6 +83,10 @@ class ClientEngine {
|
||||
|
||||
/** our actual heartbeat pumper - this drives the test */
|
||||
private class ClientRunner implements Runnable {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
|
@ -1,18 +1,16 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Iterator;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the heartbeat engine, loading 0 or more tests, firing
|
||||
@ -73,7 +71,10 @@ public class Heartbeat {
|
||||
/** if there are no command line arguments, load the config from "heartbeat.config" */
|
||||
public static final String CONFIG_FILE_DEFAULT = "heartbeat.config";
|
||||
|
||||
/** build up a new heartbeat manager, but don't actually do anything */
|
||||
/**
|
||||
* build up a new heartbeat manager, but don't actually do anything
|
||||
* @param configFile the name of the configuration file
|
||||
*/
|
||||
public Heartbeat(String configFile) {
|
||||
_configFile = configFile;
|
||||
_clientConfigs = new HashMap();
|
||||
@ -94,8 +95,9 @@ public class Heartbeat {
|
||||
fin = new FileInputStream(_configFile);
|
||||
props.load(fin);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error reading the config data", ioe);
|
||||
}
|
||||
} finally {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
@ -119,18 +121,25 @@ public class Heartbeat {
|
||||
_adapter.sendPing(peer, seriesNum, now, size);
|
||||
}
|
||||
|
||||
/** load up the base data (I2CP config, etc) */
|
||||
/**
|
||||
* load up the base data (I2CP config, etc)
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
private void loadBaseConfig(Properties props) {
|
||||
_adapter.loadConfig(props);
|
||||
}
|
||||
|
||||
/** load up all of the test config data */
|
||||
/**
|
||||
* load up all of the test config data
|
||||
* @param props the properties to load from
|
||||
* */
|
||||
private void loadClientConfigs(Properties props) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
ClientConfig config = new ClientConfig();
|
||||
if (!config.load(props, i))
|
||||
break;
|
||||
if (!config.load(props, i)) {
|
||||
break;
|
||||
}
|
||||
_clientConfigs.put(new Integer(i), config);
|
||||
i++;
|
||||
}
|
||||
@ -174,14 +183,17 @@ public class Heartbeat {
|
||||
* running any tests. <p />
|
||||
*
|
||||
* <code> <b>Usage: </b> Heartbeat [<i>configFileName</i>]</code> <p />
|
||||
* @param args the list of args passed to the program from the command-line
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String configFile = CONFIG_FILE_DEFAULT;
|
||||
if (args.length == 1)
|
||||
if (args.length == 1) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Starting up with config file " + configFile);
|
||||
}
|
||||
Heartbeat heartbeat = new Heartbeat(configFile);
|
||||
heartbeat.loadConfig();
|
||||
heartbeat.connect();
|
||||
@ -210,8 +222,9 @@ public class Heartbeat {
|
||||
* @param data arbitrary payload data
|
||||
*/
|
||||
public void receivePing(Destination from, int seriesNum, Date sentOn, byte[] data) {
|
||||
if (_adapter.getIsConnected())
|
||||
_adapter.sendPong(from, seriesNum, sentOn, data);
|
||||
if (_adapter.getIsConnected()) {
|
||||
_adapter.sendPong(from, seriesNum, sentOn, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,8 +238,9 @@ public class Heartbeat {
|
||||
*/
|
||||
public void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte[] data) {
|
||||
ClientEngine engine = (ClientEngine)_clientEngines.get(new Integer(seriesNum));
|
||||
if (engine.getPeer().equals(from))
|
||||
engine.receivePong(sentOn.getTime(), replyOn.getTime());
|
||||
if (engine.getPeer().equals(from)) {
|
||||
engine.receivePong(sentOn.getTime(), replyOn.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,26 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.I2PException;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Tie-in to the I2P SDK for the Heartbeat system, talking to the I2PSession and
|
||||
@ -69,6 +67,9 @@ class I2PAdapter {
|
||||
/** by default, use 2 hop tunnels */
|
||||
public static final int NUMHOPS_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Constructs an I2PAdapter . . .
|
||||
*/
|
||||
public I2PAdapter() {
|
||||
_privateDestFile = null;
|
||||
_i2cpHost = null;
|
||||
@ -79,22 +80,40 @@ class I2PAdapter {
|
||||
_numHops = 0;
|
||||
}
|
||||
|
||||
/** who are we? */
|
||||
/**
|
||||
* who are we?
|
||||
* @return the destination (us)
|
||||
*/
|
||||
public Destination getLocalDestination() { return _localDest; }
|
||||
|
||||
/** who gets notified when we receive a ping or a pong? */
|
||||
/**
|
||||
* who gets notified when we receive a ping or a pong?
|
||||
* @return the event listener who gets notified
|
||||
*/
|
||||
public PingPongEventListener getListener() { return _listener; }
|
||||
|
||||
|
||||
/**
|
||||
* Sets who gets notified when we receive a ping or a pong
|
||||
* @param listener the event listener to get notified
|
||||
*/
|
||||
public void setListener(PingPongEventListener listener) { _listener = listener; }
|
||||
|
||||
/** how many hops do we want in our tunnels? */
|
||||
/**
|
||||
* how many hops do we want in our tunnels?
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() { return _numHops; }
|
||||
|
||||
/** are we connected? */
|
||||
/**
|
||||
* are we connected?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getIsConnected() { return _session != null; }
|
||||
|
||||
/**
|
||||
* Read in all of the config data
|
||||
*
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
void loadConfig(Properties props) {
|
||||
String privDestFile = props.getProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
@ -106,16 +125,18 @@ class I2PAdapter {
|
||||
try {
|
||||
portNum = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid I2CP port specified [" + port + "]");
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
portNum = I2CP_PORT_DEFAULT;
|
||||
}
|
||||
int hops = -1;
|
||||
try {
|
||||
hops = Integer.parseInt(numHops);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid # hops specified [" + numHops + "]");
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid # hops specified [" + numHops + "]");
|
||||
}
|
||||
hops = NUMHOPS_DEFAULT;
|
||||
}
|
||||
|
||||
@ -125,21 +146,30 @@ class I2PAdapter {
|
||||
_i2cpPort = portNum;
|
||||
}
|
||||
|
||||
/** write out the config to the props */
|
||||
/**
|
||||
* write out the config to the props
|
||||
* @param props the properties to write to
|
||||
*/
|
||||
void storeConfig(Properties props) {
|
||||
if (_privateDestFile != null)
|
||||
if (_privateDestFile != null) {
|
||||
props.setProperty(DEST_FILE_PROP, _privateDestFile);
|
||||
else
|
||||
} else {
|
||||
props.setProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
if (_i2cpHost != null)
|
||||
}
|
||||
|
||||
if (_i2cpHost != null) {
|
||||
props.setProperty(I2CP_HOST_PROP, _i2cpHost);
|
||||
else
|
||||
} else {
|
||||
props.setProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
if (_i2cpPort > 0)
|
||||
}
|
||||
|
||||
if (_i2cpPort > 0) {
|
||||
props.setProperty(I2CP_PORT_PROP, ""+_i2cpPort);
|
||||
else
|
||||
} else {
|
||||
props.setProperty(I2CP_PORT_PROP, ""+I2CP_PORT_DEFAULT);
|
||||
props.setProperty(NUMHOPS_PROP, ""+_numHops);
|
||||
}
|
||||
|
||||
props.setProperty(NUMHOPS_PROP, ""+_numHops);
|
||||
}
|
||||
|
||||
private static final int TYPE_PING = 0;
|
||||
@ -170,21 +200,26 @@ class I2PAdapter {
|
||||
baos.write(paddingData);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending the ping", ioe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing out the ping message", dfe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing out the ping message", ise);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,27 +246,33 @@ class I2PAdapter {
|
||||
baos.write(data);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the pong to " + peer.calculateHash().toBase64() + " for series " + seriesNum + " which was sent on " + sentOn);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum + " which was sent on " + sentOn);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending the ping", ioe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing out the pong message", dfe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing out the pong message", ise);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received this data from I2P - parse it into a ping or a pong
|
||||
* and notify accordingly
|
||||
* @param data the data to handle
|
||||
*/
|
||||
private void handleMessage(byte data[]) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
@ -243,38 +284,44 @@ class I2PAdapter {
|
||||
Date sentOn = DataHelper.readDate(bais);
|
||||
Date receivedOn = null;
|
||||
if (type == TYPE_PONG) {
|
||||
receivedOn = DataHelper.readDate(bais);
|
||||
receivedOn = DataHelper.readDate(bais);
|
||||
}
|
||||
int size = (int)DataHelper.readLong(bais, 2);
|
||||
byte payload[] = new byte[size];
|
||||
int read = DataHelper.read(bais, payload);
|
||||
if (read != size)
|
||||
throw new IOException("Malformed payload - read " + read + " instead of " + size);
|
||||
if (read != size) {
|
||||
throw new IOException("Malformed payload - read " + read + " instead of " + size);
|
||||
}
|
||||
|
||||
if (_listener == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Listener isn't set, but we received a valid message of type " + type + " sent from " + from.calculateHash().toBase64());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == TYPE_PING) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping received from " + from.calculateHash().toBase64() + " on series " + series + " sent on " + sentOn + " containing " + size + " bytes");
|
||||
}
|
||||
_listener.receivePing(from, series, sentOn, payload);
|
||||
} else if (type == TYPE_PONG) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong received from " + from.calculateHash().toBase64() + " on series " + series + " sent on " + sentOn + " with pong sent on " + receivedOn + " containing " + size + " bytes");
|
||||
}
|
||||
_listener.receivePong(from, series, sentOn, receivedOn, payload);
|
||||
} else {
|
||||
throw new IOException("Invalid message type " + type);
|
||||
throw new IOException("Invalid message type " + type);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error handling the message", ioe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error handling the message", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error parsing the message", dfe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error parsing the message", dfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,17 +350,20 @@ class I2PAdapter {
|
||||
session.setSessionListener(lsnr);
|
||||
session.connect();
|
||||
_localDest = session.getMyDestination();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
|
||||
}
|
||||
_session = session;
|
||||
_i2pListener = lsnr;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error connecting", ise);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error connecting", ise);
|
||||
}
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error loading the destionation", ioe);
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error loading the destionation", ioe);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe) {}
|
||||
@ -325,6 +375,8 @@ class I2PAdapter {
|
||||
/**
|
||||
* load, verify, or create a destination
|
||||
*
|
||||
* @param client the client
|
||||
* @param destFile the file holding the destination
|
||||
* @return the destination loaded, or null if there was an error
|
||||
*/
|
||||
private Destination verifyDestination(I2PClient client, File destFile) {
|
||||
@ -335,8 +387,9 @@ class I2PAdapter {
|
||||
fin = new FileInputStream(destFile);
|
||||
us = new Destination();
|
||||
us.readBytes(fin);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Existing destination loaded: [" + us.toBase64() + "]");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (fin != null) try { fin.close(); } catch (IOException ioe2) {}
|
||||
fin = null;
|
||||
@ -359,15 +412,18 @@ class I2PAdapter {
|
||||
try {
|
||||
fos = new FileOutputStream(destFile);
|
||||
us = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("New destination created: [" + us.toBase64() + "]");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the destination keys being created", ioe);
|
||||
}
|
||||
return null;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error creating the destination", ie);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
@ -378,6 +434,7 @@ class I2PAdapter {
|
||||
|
||||
/**
|
||||
* I2PSession connect options
|
||||
* @return the options as Properties
|
||||
*/
|
||||
private Properties getOptions() {
|
||||
Properties props = new Properties();
|
||||
@ -395,8 +452,9 @@ class I2PAdapter {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error destroying the session", ise);
|
||||
}
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
@ -434,20 +492,34 @@ class I2PAdapter {
|
||||
*
|
||||
*/
|
||||
private class I2PListener implements I2PSessionListener {
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Session disconnected");
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#disconnected(net.i2p.client.I2PSession)
|
||||
*/
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Session disconnected");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#errorOccurred(net.i2p.client.I2PSession, java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error occurred", error);
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#reportAbuse(net.i2p.client.I2PSession, int)
|
||||
*/
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Abuse reported");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#messageAvailable(net.i2p.client.I2PSession, int, long)
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
|
@ -1,17 +1,17 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Contain the current window of data for a particular series of ping/pong stats
|
||||
* sent to a peer. This should be periodically kept clean by calling cleanup()
|
||||
@ -42,6 +42,10 @@ public class PeerData {
|
||||
/** synchronize on this when updating _dataPoints or _pendingPings */
|
||||
private Object _updateLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a PeerData . . .
|
||||
* @param config configuration to load from
|
||||
*/
|
||||
public PeerData(ClientConfig config) {
|
||||
_peer = config;
|
||||
_dataPoints = new TreeMap();
|
||||
@ -54,7 +58,11 @@ public class PeerData {
|
||||
_lostRate = new RateStat("lostRate", "How frequently we lose messages", "peer", getPeriods(config.getAveragePeriods()));
|
||||
}
|
||||
|
||||
/** turn the periods (# minutes) into rate periods (# milliseconds) */
|
||||
/**
|
||||
* turn the periods (# minutes) into rate periods (# milliseconds)
|
||||
* @param periods (in minutes)
|
||||
* @return an array of periods (in milliseconds)
|
||||
*/
|
||||
private static long[] getPeriods(int periods[]) {
|
||||
long rv[] = null;
|
||||
if (periods == null) periods = new int[0];
|
||||
@ -65,22 +73,48 @@ public class PeerData {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** how many pings are still outstanding? */
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() { synchronized (_updateLock) { return _pendingPings.size(); } }
|
||||
/** how many data points are available in the current window? */
|
||||
|
||||
/**
|
||||
* how many data points are available in the current window?
|
||||
* @return the number of datapoints available
|
||||
*/
|
||||
public int getDataPointCount() { synchronized (_updateLock) { return _dataPoints.size(); } }
|
||||
/** when did this test begin? */
|
||||
|
||||
/**
|
||||
* when did this test begin?
|
||||
* @return when the test began
|
||||
*/
|
||||
public long getSessionStart() { return _sessionStart; }
|
||||
/** how many pings have we sent for this test? */
|
||||
|
||||
/**
|
||||
* how many pings have we sent for this test?
|
||||
* @return the number of pings sent
|
||||
*/
|
||||
public long getLifetimeSent() { return _lifetimeSent; }
|
||||
/** how many pongs have we received for this test? */
|
||||
|
||||
/**
|
||||
* how many pongs have we received for this test?
|
||||
* @return the number of pings received
|
||||
*/
|
||||
public long getLifetimeReceived() { return _lifetimeReceived; }
|
||||
|
||||
|
||||
/**
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getConfig() { return _peer; }
|
||||
|
||||
/**
|
||||
* What periods are we averaging the data over (in minutes)?
|
||||
* @return the periods as an array of ints (in minutes)
|
||||
*/
|
||||
public int[] getAveragePeriods() { return (_peer.getAveragePeriods() != null ? _peer.getAveragePeriods() : new int[0]); }
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
@ -88,6 +122,7 @@ public class PeerData {
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) { return getAverage(_sendRate, period); }
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
@ -95,6 +130,7 @@ public class PeerData {
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) { return getAverage(_receiveRate, period); }
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
@ -129,7 +165,7 @@ public class PeerData {
|
||||
|
||||
/**
|
||||
* We have sent the peer a ping on this series (using the send time as given)
|
||||
*
|
||||
* @param dateSent when the ping was sent
|
||||
*/
|
||||
public void addPing(long dateSent) {
|
||||
EventDataPoint sent = new EventDataPoint(dateSent);
|
||||
@ -239,9 +275,17 @@ public class PeerData {
|
||||
private long _pongSent;
|
||||
private long _pongReceived;
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint
|
||||
*/
|
||||
public EventDataPoint() {
|
||||
this(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint with pingtime associated with it =)
|
||||
* @param pingSentOn the time a ping was sent
|
||||
*/
|
||||
public EventDataPoint(long pingSentOn) {
|
||||
_wasPonged = false;
|
||||
_pingSent = pingSentOn;
|
||||
@ -249,20 +293,51 @@ public class PeerData {
|
||||
_pongReceived = -1;
|
||||
}
|
||||
|
||||
/** when did we send this ping? */
|
||||
/**
|
||||
* when did we send this ping?
|
||||
* @return the time the ping was sent
|
||||
*/
|
||||
public long getPingSent() { return _pingSent; }
|
||||
public void setPingSent(long when) { _pingSent = when; }
|
||||
|
||||
/** when did the peer receive the ping? */
|
||||
/**
|
||||
* Set the time the ping was sent
|
||||
* @param when time to set
|
||||
*/
|
||||
public void setPingSent(long when) { _pingSent = when; }
|
||||
|
||||
/**
|
||||
* when did the peer receive the ping?
|
||||
* @return the time the ping was receieved
|
||||
*/
|
||||
public long getPongSent() { return _pongSent; }
|
||||
|
||||
/**
|
||||
* Set the time the peer received the ping
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongSent(long when) { _pongSent = when; }
|
||||
|
||||
/** when did we receive the peer's pong? */
|
||||
/**
|
||||
* when did we receive the peer's pong?
|
||||
* @return the time we receieved the pong
|
||||
*/
|
||||
public long getPongReceived() { return _pongReceived; }
|
||||
|
||||
/**
|
||||
* Set the time the peer's pong was receieved
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongReceived(long when) { _pongReceived = when; }
|
||||
|
||||
/** did the peer reply in time? */
|
||||
/**
|
||||
* did the peer reply in time?
|
||||
* @return true or false, whether we got a reply in time */
|
||||
public boolean getWasPonged() { return _wasPonged; }
|
||||
|
||||
/**
|
||||
* Set whether we receieved the peer's reply in time
|
||||
* @param pong true or false
|
||||
*/
|
||||
public void setWasPonged(boolean pong) { _wasPonged = pong; }
|
||||
}
|
||||
}
|
@ -1,18 +1,17 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.io.IOException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Locale;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Actually write out the stats for peer test
|
||||
@ -24,6 +23,7 @@ class PeerDataWriter {
|
||||
/**
|
||||
* persist the peer state to the location specified in the peer config
|
||||
*
|
||||
* @param data the peer data to persist
|
||||
* @return true if it was persisted correctly, false on error
|
||||
*/
|
||||
public boolean persist(PeerData data) {
|
||||
@ -93,12 +93,25 @@ class PeerDataWriter {
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
private static final DecimalFormat _numFmt = new DecimalFormat("#0", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getNum(double val) {
|
||||
synchronized (_numFmt) {
|
||||
return _numFmt.format(val);
|
||||
|
Reference in New Issue
Block a user