private JLabel arbitrageResult = null;
private int unknownPCTBcommods = 0;
private long startTime = 0;
+ private ProgressMonitor progmon = null;
private final static String PCTB_LIVE_HOST_URL = "http://pctb.crabdance.com/";
private final static String PCTB_TEST_HOST_URL = "http://pctb.ilk.org/";
private HashMap<String,Integer> commodMap;
public PrintStream dtxt = null;
- private PropertyChangeListener changeListener = new PropertyChangeListener() {
- public void propertyChange(PropertyChangeEvent e) {
- if(e.getNewValue() != null &&
- e.getPropertyName().equals
- (AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) {
- Accessible islandInfo =
- descendNodes(window,new int[] {0,1,0,0,2,2,0,0,0,0,1,2});;
- String text =
- islandInfo.getAccessibleContext().getAccessibleText()
- .getAtIndex(AccessibleText.SENTENCE,0);
- int index = text.indexOf(":");
- String name = text.substring(0,index);
- islandName = name;
- // if (dtxt!=null) dtxt.println(islandName);
- sidePanel.removePropertyChangeListener(this);
- latch.countDown();
- }
- }
- };
-
/*
* UTILITY METHODS AND SUBCLASSES
*
}
}
- private void progresslog(String s) {
+ private void debuglog(String s) {
if (dtxt == null) return;
long now = new Date().getTime();
dtxt.println("progress "+(now - startTime)+"ms "+s);
}
}
- private void progressNote(ProgressMonitor pm, String s) {
- String arb = null;
- new UI() { public void body() {
- if (arbitrageResult != null)
- arb = arbitrageResult.getText();
+ private void progressNote(final String s_in) throws Exception {
+ new UIX() { public void body() {
+ String arb = null;
+ arb = arbitrageResult.getText();
+ String s = s_in;
if (arb != null && arb.length() != 0)
s = "<html>" + arb + "<br>" + s;
- pm.setNote(s);
+ progmon.setNote(s);
}}.exec();
}
uploadToPCTB=prefs.getBoolean("uploadToPCTB", true);
showArbitrage=prefs.getBoolean("showArbitrage", true);
- if (dtxt!=null) dtxt.println("main on dispatch thread: "+
- EventQueue.isDispatchThread());
+ debuglog("main on dispatch thread: "+EventQueue.isDispatchThread());
EventQueue.invokeLater(this);
}
* every top-level window, and if it
*/
public void run() {
- if (dtxt!=null) dtxt.println("MarketUploader run()...");
+ debuglog("MarketUploader run()...");
if (EventQueueMonitor.isGUIInitialized()) {
- if (dtxt!=null) dtxt.println("MarketUploader GUI already ready");
+ debuglog("MarketUploader GUI already ready");
guiInitialized();
} else {
- if (dtxt!=null) dtxt.println("MarketUploader waiting for GUI");
+ debuglog("MarketUploader waiting for GUI");
EventQueueMonitor.addGUIInitializedListener(this);
}
}
Window ws[]= EventQueueMonitor.getTopLevelWindows();
EventQueueMonitor.addTopLevelWindowListener(this);
for (int i=0; i<ws.length; i++) {
- if (dtxt!=null) dtxt.println("MarketUploader existing toplevel "+i);
+ debuglog("MarketUploader existing toplevel "+i);
topLevelWindowCreated(ws[i]);
}
}
public void topLevelWindowDestroyed(Window w) {
- if (dtxt!=null) dtxt.println("MarketUploader destroyed toplevel");
+ debuglog("MarketUploader destroyed toplevel");
}
public void topLevelWindowCreated(Window w) {
// already got it
return;
String name = w.getAccessibleContext().getAccessibleName();
- if (dtxt!=null) dtxt.println("MarketUploader new toplevel "+name);
+ debuglog("MarketUploader new toplevel "+name);
if (!name.equals("Puzzle Pirates"))
return;
- if (dtxt!=null) dtxt.println("MarketUploader found toplevel, creating gui");
+ debuglog("MarketUploader found toplevel, creating gui");
window = w;
createGUI();
frame.setVisible(true);
* a Window named "Puzzle Pirates" though.
*/
private void createGUI() {
- // on event thread
+ on_ui_thread();
frame = new JFrame("Jarrg Uploader");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
GridLayout layout = new GridLayout(2,1);
findMarket = new JButton("Upload Market Data");
findMarket.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
+ on_ui_thread();
findMarket.setEnabled(false);
resultSummary.setText("");
arbitrageResult.setText("");
error(e.toString());
e.printStackTrace();
}
- EventQueue.invokeAndWait(new Runnable() { public void run() {
- if(sidePanel != null) {
- sidePanel.removePropertyChangeListener(changeListener);
- }
- findMarket.setEnabled(true);
- }});
+ try {
+ new UIX() { public void body() {
+ if(sidePanel != null) {
+ sidePanel.removePropertyChangeListener(changeListener);
+ }
+ if (progmon != null) {
+ progmon.close();
+ progmon = null;
+ }
+ findMarket.setEnabled(true);
+ }}.exec();
+ } catch (Exception e) {
+ System.err.println("exception tidying on UI thread:");
+ e.printStackTrace();
+ }
}
}.start();
}
* error and error_html may be called from any thread
*/
- private Class UI implements Runnable {
- public virtual void body();
- public void run() { body(); };
- public void exec() {
+ private abstract class UIXR<ReturnType> implements Runnable {
+ public abstract ReturnType bodyr();
+ public ReturnType return_value;
+ public void run() { return_value = bodyr(); };
+ public ReturnType exec() throws Exception {
if (EventQueue.isDispatchThread()) {
- r.run();
+ this.run();
} else {
- invokeAndWait(r);
+ EventQueue.invokeAndWait(this);
}
+ return return_value;
};
};
+ private abstract class UIX extends UIXR<Object> implements Runnable {
+ public abstract void body();
+ public Object bodyr() { body(); return null; }
+ };
- private void error(String msg) {
- new UI() { public booolean body() {
- resultSummary.setText("failed");
- JOptionPane.showMessageDialog(frame,msg,"Error",
- JOptionPane.ERROR_MESSAGE);
- return true;
- }}.exec();
+ private void on_ui_thread() { assert(EventQueue.isDispatchThread()); }
+ private void on_our_thread() { assert(!EventQueue.isDispatchThread()); }
+
+ private void error(final String msg) {
+ try {
+ new UIX() { public void body() {
+ resultSummary.setText("failed");
+ JOptionPane.showMessageDialog(frame,msg,"Error",
+ JOptionPane.ERROR_MESSAGE);
+ }}.exec();
+ } catch (Exception e) {
+ System.err.println("exception reporting to UI thread:");
+ e.printStackTrace();
+ }
}
- private void error_html(String msg, String html) {
+ private void error_html(final String msg, String html) {
Pattern body = Pattern.compile("<body>(.*)</body>",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
Matcher m = body.matcher(html);
}
String whole_msg = "<html><h1>Error</h1>"+msg
+"<h1>PCTB Server said:</h1><blockquote>"+html+"</blockquote>";
- if (dtxt!=null) dtxt.println("###" + whole_msg + "###");
+ debuglog("###" + whole_msg + "###");
error(whole_msg);
}
+
+ private void setProgress(final int nv) throws Exception {
+ new UIX() { public void body() {
+ progmon.setProgress(nv);
+ }}.exec();
+ }
+ private boolean isCanceled() throws Exception {
+ return new UIXR<Boolean>() { public Boolean bodyr() {
+ return new Boolean(progmon.isCanceled());
+ }}.exec().booleanValue();
+ }
/*
* GUI MANIPULATION CALLBACKS
private PropertyChangeListener changeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
+ on_ui_thread();
if(e.getNewValue() != null &&
e.getPropertyName().equals
(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) {
int index = text.indexOf(":");
String name = text.substring(0,index);
islandName = name;
- // if (dtxt!=null) dtxt.println(islandName);
+ // debuglog(islandName);
sidePanel.removePropertyChangeListener(this);
latch.countDown();
}
};
private void getIsland() {
- // runs on event thread
+ on_ui_thread();
// If the league tracker is there, we can skip the faff
// and ask for its tooltip, since we're on a boat
* oceanName variable
*/
private void getOcean() {
- // runs on event thread
+ on_ui_thread();
oceanName = null;
AccessibleContext topwindow = window.getAccessibleContext();
oceanName = topwindow.getAccessibleName()
public void run() {
try {
ts = getYarrgTimestamp();
- progresslog("(async) yarrg timestamp ready.");
+ debuglog("(async) yarrg timestamp ready.");
} catch(Exception e) {
error("Error getting YARRG timestamp: "+e);
}
};
private void runUpload() throws Exception {
+ on_our_thread();
+
boolean doneyarrg = false, donepctb = false;
YarrgTimestampFetcher yarrgts_thread = null;
- progresslog("starting");
+ debuglog("starting");
if (uploadToYarrg) {
- progresslog("(async) yarrg timestamp...");
+ debuglog("(async) yarrg timestamp...");
yarrgts_thread = new YarrgTimestampFetcher();
yarrgts_thread.start();
}
- AccessibleTable accesstable = null;
- new UI() { public boolean body() {
- ProgressMonitor pm = new ProgressMonitor
+ final AccessibleTable accesstable =
+ new UIXR<AccessibleTable>() { public AccessibleTable bodyr() {
+ progmon = new ProgressMonitor
(frame,"Processing Market Data","Getting table data",0,100);
- pm.setMillisToDecideToPopup(0);
- pm.setMillisToPopup(0);
+ progmon.setMillisToDecideToPopup(0);
+ progmon.setMillisToPopup(0);
- accesstable = findMarketTable();
- if(accesstable == null) {
+ AccessibleTable at = findMarketTable();
+ if(at == null) {
error("Market table not found!"+
" Please open the Buy/Sell Commodities interface.");
- return;
+ return null;
}
- if(accesstable.getAccessibleRowCount() == 0) {
+ if(at.getAccessibleRowCount() == 0) {
error("No data found, please wait for the table to have data first!");
- return;
+ return null;
}
if(!isDisplayAll()) {
error("Please select \"All\" from the Display: popup menu.");
- return;
+ return null;
}
- progresslog("(async) getisland...");
+ debuglog("(async) getisland...");
getIsland();
- progresslog("getocean...");
+ debuglog("getocean...");
getOcean();
- progresslog("getocean done");
+ debuglog("getocean done");
+
+ return at;
}}.exec();
if (accesstable == null) return;
if (latch != null) {
latch.await(2, java.util.concurrent.TimeUnit.SECONDS);
}
- progresslog("(async) getisland done");
+ debuglog("(async) getisland done");
String yarrgts = null;
if (yarrgts_thread != null) {
- progresslog("(async) yarrg timestamp join...");
+ debuglog("(async) yarrg timestamp join...");
yarrgts_thread.join();
- progresslog("(async) yarrg timestamp joined.");
+ debuglog("(async) yarrg timestamp joined.");
yarrgts = yarrgts_thread.ts;
}
return;
}
- progresslog("table check...");
+ debuglog("table check...");
- ArrayList<ArrayList<String>> data = null;
- String headings_expected[] = new String[]
- { "Commodity", "Trading outlet", "Buy price",
- "Will buy", "Sell price", "Will sell" };
+ final ArrayList<ArrayList<String>> data =
+ new UIXR<ArrayList<ArrayList<String>>>
+ () { public ArrayList<ArrayList<String>> bodyr() {
+ String headings_expected[] = new String[]
+ { "Commodity", "Trading outlet", "Buy price",
+ "Will buy", "Sell price", "Will sell" };
- new UI() { public void body() {
ArrayList<ArrayList<String>> headers =
getData(accesstable.getAccessibleColumnHeader());
if (headers.size() != 1) {
error("Table headings not one row! " + headers.toString());
- return;
+ return null;
}
if (headers.get(0).size() < 6 ||
headers.get(0).size() > 7) {
error("Table headings not six or seven columns! " + headers.toString());
- return;
+ return null;
}
for (int col=0; col<headings_expected.length; col++) {
String expd = headings_expected[col];
error("Table heading for column "+col
+" is not \""+expd+"\" but \""+got+"\".\n\n"
+"Please do not reorder the table when using this tool.");
- return;
+ return null;
}
}
- progresslog("table read...");
+ debuglog("table read...");
- ArrayList<ArrayList<String>> data = getData(accesstable);
+ return getData(accesstable);
}}.exec();
- if (!data) return;
+ if (data == null) return;
if (showArbitrage) {
- progresslog("arbitrage...");
+ debuglog("arbitrage...");
calculateArbitrage(data);
- progresslog("arbitrage done.");
+ debuglog("arbitrage done.");
}
if (uploadToYarrg && yarrgts != null) {
- progresslog("yarrg prepare...");
- progressNote(pm, "Yarrg: Preparing data");
- pm.setProgress(10);
+ debuglog("yarrg prepare...");
+ progressNote("Yarrg: Preparing data");
+ setProgress(10);
StringBuilder yarrgsb = new StringBuilder();
String yarrgdata; // string containing what we'll feed to yarrg
yarrgdata = yarrgsb.toString();
- progressNote(pm, "Yarrg: Uploading");
- progresslog("yarrg upload...");
+ progressNote("Yarrg: Uploading");
+ debuglog("yarrg upload...");
doneyarrg = runYarrg(yarrgts, oceanName, islandName, yarrgdata);
- progresslog("yarrg done.");
+ debuglog("yarrg done.");
}
if (uploadToPCTB) {
- progresslog("pctb prepare...");
- progressNote(pm, "PCTB: Getting stall names");
- pm.setProgress(20);
- if(pm.isCanceled()) {
+ debuglog("pctb prepare...");
+ progressNote("PCTB: Getting stall names");
+ setProgress(20);
+ if(isCanceled()) {
return;
}
TreeSet<Offer> buys = new TreeSet<Offer>();
TreeSet<Offer> sells = new TreeSet<Offer>();
LinkedHashMap<String,Integer> stallMap = getStallMap(data);
- pm.setProgress(40);
- progressNote(pm, "PCTB: Sorting offers");
- if(pm.isCanceled()) {
+ setProgress(40);
+ progressNote("PCTB: Sorting offers");
+ if(isCanceled()) {
return;
}
// get commod map
- progresslog("pctb commodmap...");
+ debuglog("pctb commodmap...");
HashMap<String,Integer> commodMap = getCommodMap();
if(commodMap == null) {
return;
}
- progresslog("pctb commodmap done.");
+ debuglog("pctb commodmap done.");
int[] offerCount = getBuySellMaps(data,buys,sells,stallMap,commodMap);
- // if (dtxt!=null) dtxt.println(sells);
- // if (dtxt!=null) dtxt.println("\n\n\n"+buys);
+ // debuglog(sells);
+ // debuglog("\n\n\n"+buys);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- pm.setProgress(60);
- progressNote(pm, "PCTB: Sending data");
- if(pm.isCanceled()) {
+ setProgress(60);
+ progressNote("PCTB: Sending data");
+ if(isCanceled()) {
return;
}
GZIPOutputStream out = new GZIPOutputStream(outStream);
dos.writeBytes(getAbbrevStallList(stallMap));
writeBuySellOffers(buys,sells,offerCount,out);
out.finish();
- progresslog("pctb send...");
+ debuglog("pctb send...");
byte[] ba = outStream.toByteArray();
debug_write_bytes("pctb-marketdata.gz", ba);
InputStream in = sendInitialData(new ByteArrayInputStream(ba));
- progresslog("pctb sent.");
+ debuglog("pctb sent.");
if (in == null) return;
- pm.setProgress(80);
- if(pm.isCanceled()) {
+ setProgress(80);
+ if(isCanceled()) {
return;
}
- progressNote(pm, "PCTB: Waiting ...");
- progresslog("pctb finish...");
+ progressNote("PCTB: Waiting ...");
+ debuglog("pctb finish...");
donepctb = finishUpload(in);
- progresslog("pctb done.");
+ debuglog("pctb done.");
}
- pm.setProgress(100);
+ setProgress(99);
+ String summary;
if ((uploadToPCTB && !donepctb) ||
(uploadToYarrg && !doneyarrg)) {
- resultSummary.setText("trouble");
+ summary= "trouble";
} else if (unknownPCTBcommods != 0) {
- resultSummary.setText("PCTB lacks "+unknownPCTBcommods+" commod");
+ summary= "PCTB lacks "+unknownPCTBcommods+" commod(s)";
} else if (donepctb || doneyarrg) {
- resultSummary.setText("Done " + islandName);
+ summary= "Done " + islandName;
} else {
- resultSummary.setText("uploaded nowhere!");
+ summary= "uploaded nowhere!";
}
- progresslog("done.");
+ final String summary_final = summary;
+ new UIX() { public void body() {
+ resultSummary.setText(summary_final);
+ }}.exec();
+
+ debuglog("done.");
}
/**
* @return an array of record arrays, each representing a row of the table
*/
private ArrayList<ArrayList<String>> getData(AccessibleTable table) {
+ on_ui_thread();
ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
for (int i = 0; i < table.getAccessibleRowCount(); i++) {
ArrayList<String> row = new ArrayList<String>();
* otherwise <code>null</code>
*/
public AccessibleTable findMarketTable() {
+ on_ui_thread();
Accessible node1 = window;
Accessible node = descendNodes(node1,new int[] {0,1,0,0,0,0,1,0,0,1,0,0});
// commod market
// commod market: {0,1,0,0,0,0,1,0,0,1,0} {0,1,0,0,0,0,1,0,1,0,0,1,0,0})
- // if (dtxt!=null) dtxt.println(node);
+ // debuglog(node);
if (!(node instanceof JTable)) {
node = descendNodes(node1,new int[] {0,1,0,0,0,0,1,0,1,0,0,1,0,0});
// commod market
}
if (!(node instanceof JTable)) return null;
AccessibleTable table = node.getAccessibleContext().getAccessibleTable();
- // if (dtxt!=null) dtxt.println(table);
+ // debuglog(table);
return table;
}
* invalid.
*/
private Accessible descendNodes(Accessible parent, int[] path) {
+ on_ui_thread();
for(int i=0;i<path.length;i++) {
if (null == (parent = descend(parent, path[i]))) return null;
}
* or <code>null</code> if the child is not found.
*/
private Accessible descend(Accessible parent, int childNum) {
+ on_ui_thread();
if (parent == null) return null;
int children = parent.getAccessibleContext().getAccessibleChildrenCount();
if (childNum >= children) {
- if (dtxt!=null) dtxt.println("DESCEND "+childNum+" > "
- +children+" NOT FOUND");
+ debuglog("DESCEND "+childNum+" > "+children+" NOT FOUND");
return null;
}
Accessible child = parent.getAccessibleContext()
.getAccessibleChild(childNum);
- if (dtxt!=null) dtxt.println("DESCEND "+childNum+" "
- +child.getClass().getName()+" OK");
+ debuglog("DESCEND "+childNum+" "+child.getClass().getName()+" OK");
return child;
}
* @return the child or <code>null</code> if the child is not found.
*/
private Accessible descendByClass(Accessible parent, String classname) {
+ on_ui_thread();
if (parent == null) return null;
AccessibleContext ac = parent.getAccessibleContext();
int children = ac.getAccessibleChildrenCount();
for (int i=0; i<children; i++) {
Accessible child = ac.getAccessibleChild(i);
if (child.getClass().getName() == classname) {
- if (dtxt!=null) dtxt.println("DESCEND CLASS "+classname+" OK");
+ debuglog("DESCEND CLASS "+classname+" OK");
return child;
}
}
- if (dtxt!=null) dtxt.println("DESCEND CLASS "+classname+" NOT FOUND");
+ debuglog("DESCEND CLASS "+classname+" NOT FOUND");
return null;
}
* otherwise <code>false</code>
*/
private boolean isDisplayAll() {
+ on_ui_thread();
Accessible button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,0,0,1});
if(!(button instanceof JButton)) {
button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,1,0,0,0,1});
* the commodity id.
*/
private HashMap<String,Integer> getCommodMap() {
+ on_our_thread();
if(commodMap != null) {
return commodMap;
}
}
} catch(IllegalArgumentException e) {
unknownPCTBcommods++;
- if (dtxt!=null) dtxt.println("Error: Unsupported Commodity \""
- + offer.get(0) + "\"");
+ debuglog("Error: Unsupported Commodity \"" + offer.get(0) + "\"");
}
}
if (buySellCount[0]==0 && buySellCount[1]==0) {
* @param file an InputStream open to the gzipped data we want to send
*/
private InputStream sendInitialData(InputStream file) throws IOException {
+ on_our_thread();
ClientHttpRequest http =
new ClientHttpRequest(PCTB_HOST_URL + "upload.php");
http.setParameter("marketdata","marketdata.gz",file,"application/gzip");
* @param in stream of data from the server to read
*/
private boolean finishUpload(InputStream in) throws IOException {
+ on_our_thread();
+
String html = readstreamstring(in);
debug_write_stringdata("pctb-initial.html", html);
Matcher m;
private InputStream post_for_yarrg(ClientHttpRequest http)
throws IOException {
+ on_our_thread();
if (!http.post()) {
String err = readstreamstring(http.resultstream());
error("<html><h1>Error reported by YARRG server</h1>\n" + err);
}
private int calculateArbitrageCommodity(ArrayList<SortedSet<int[]>> arb_bs) {
- // if (dtxt!=null) dtxt.println("ARBITRAGE?");
+ // debuglog("ARBITRAGE?");
int profit = 0;
SortedSet<int[]> buys = arb_bs.get(0);
SortedSet<int[]> sells = arb_bs.get(1);
int unitprofit = buy[0] - sell[0];
int count = buy[1] < sell[1] ? buy[1] : sell[1];
- // if (dtxt!=null) dtxt.println(" sell @"+sell[0]+" x"+sell[1]
- // +" buy @"+buy[0]+" x"+buy[1]
- // +" => x"+count+" @"+unitprofit);
+ // debuglog(" sell @"+sell[0]+" x"+sell[1]
+ // +" buy @"+buy[0]+" x"+buy[1]
+ // +" => x"+count+" @"+unitprofit);
if (unitprofit <= 0)
break;
if (buy[1]==0) buys.remove(buy);
if (sell[1]==0) sells.remove(sell);
}
- // if (dtxt!=null) dtxt.println(" PROFIT "+profit);
+ // debuglog(" PROFIT "+profit);
return profit;
}
}
private @SuppressWarnings("unchecked")
- void calculateArbitrage(ArrayList<ArrayList<String>> data) {
+ void calculateArbitrage(ArrayList<ArrayList<String>> data)
+ throws InterruptedException {
int arbitrage = 0;
ArrayList<SortedSet<int[]>> arb_bs = null;
String lastcommod = null;
for (ArrayList<String> row : data) {
String thiscommod = row.get(0);
- // if (dtxt!=null) dtxt.println("ROW "+row.toString());
+ // debuglog("ROW "+row.toString());
if (lastcommod == null || !thiscommod.equals(lastcommod)) {
if (lastcommod != null)
arbitrage += calculateArbitrageCommodity(arb_bs);
- // if (dtxt!=null) dtxt.println("ROW rdy");
+ // debuglog("ROW rdy");
arb_bs = new ArrayList<SortedSet<int[]>>(2);
arb_bs.add(0, new TreeSet<int[]>(compar));
arb_bs.add(1, new TreeSet<int[]>(compar));
- // if (dtxt!=null) dtxt.println("ROW init");
+ // debuglog("ROW init");
lastcommod = thiscommod;
}
for (int bs = 0; bs < 2; bs++) {
if (pricestr == null)
continue;
int[] entry = new int[2];
- // if (dtxt!=null) dtxt.println("ROW BS "+bs);
+ // debuglog("ROW BS "+bs);
entry[0] = parseQty(pricestr);
entry[1] = parseQty(row.get(bs*2 + 3));
arb_bs.get(bs).add(entry);
}
}
arbitrage += calculateArbitrageCommodity(arb_bs);
+ String arb;
if (arbitrage != 0) {
- arbitrageResult.setText("<html><strong>arbitrage: "+arbitrage
- +" poe</strong>");
+ arb = "<html><strong>arbitrage: "+arbitrage+" poe</strong>";
} else {
- arbitrageResult.setText("no arbitrage");
+ arb = "no arbitrage";
}
+ final String arb_final = arb;
+ EventQueue.invokeLater(new Runnable() { public void run() {
+ arbitrageResult.setText(arb_final);
+ }});
}
}