import java.util.prefs.Preferences;
import java.beans.*;
-/**
-* MarketUploader is a class that handles the uploading of market
-* data from Yohoho! Puzzle Pirates via the Java Accessibility
-* API.
-*
-* MarketUploader initializes after the main YPP window has
-* initialized. It provides a simple window with a "Capture
-* Market Data" button displayed. Upon clicking this button, a
-* progress dialog is displayed, and the data is processed and
-* submitted to the YARRG and PCTB servers. If any errors occur,
-* an error dialog is shown, and processing returns, the button
-* becoming re-enabled.
-*/
+/*
+ * MarketUploader is a class that handles the uploading of market
+ * data from Yohoho! Puzzle Pirates via the Java Accessibility
+ * API.
+ *
+ * MarketUploader initializes after the main YPP window has
+ * initialized. It provides a simple window with a "Capture
+ * Market Data" button displayed. Upon clicking this button, a
+ * progress dialog is displayed, and the data is processed and
+ * submitted to the YARRG and PCTB servers. If any errors occur,
+ * an error dialog is shown, and processing returns, the button
+ * becoming re-enabled.
+ */
public class MarketUploader
implements Runnable, TopLevelWindowListener, GUIInitializedListener {
+ // UI object references which are set during startup
private JFrame frame = null;
private Window window = null;
+
+ // Genuinely global variables
+ public PrintStream dtxt = null;
+ private int uploadcounter = 0;
+
+ // UI objects which are enabled/disabled, cleared/set, created/destroyed,
+ // etc., for each upload
private JButton findMarket = null;
private JLabel resultSummary = null;
private JLabel arbitrageResult = null;
- private int unknownPCTBcommods = 0;
- private long startTime = 0;
private ProgressMonitor progmon = null;
+ // PCTB protocol parameters
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 String PCTB_HOST_URL;
- // Yarrg protocol parameters
+ // YARRG protocol parameters
private final static String YARRG_CLIENTNAME = "jpctb greenend";
private final static String YARRG_CLIENTVERSION =
net.chiark.yarrg.Version.version;
"http://upload.yarrg.chiark.net/test/commod-update-receiver";
private String YARRG_URL;
+ // Preferences
private boolean uploadToYarrg;
private boolean uploadToPCTB;
private boolean showArbitrage;
+ // Values cleared/set for each upload, or used during upload processing
+ private long startTime = 0;
+
private String islandName = null;
private String oceanName = null;
private java.util.concurrent.CountDownLatch latch = null;
private AccessibleContext sidePanel;
+
+ // PCTB-specific variables
+ private int unknownPCTBcommods = 0;
private HashMap<String,Integer> commodMap;
- public PrintStream dtxt = null;
- private int uploadcounter = 0;
+
+
+ /*****************************************
+ * UPLOAD-TARGET-INDEPENDENT CODE *
+ *****************************************/
+
/*
* UTILITY METHODS AND SUBCLASSES
strm.write(data);
strm.close();
}
-
- /**
- * An abstract market offer, entailing a commodity being bought or sold by
- * a shoppe, for a certain price in a certain quantity. Not instantiable.
- *
- * @see Buy
- * @see Sell
- */
- abstract class Offer {
- public int commodity, price, quantity, shoppe;
- /**
- * Create an offer from <code>record</code>, determining the shoppe Id from
- * <code>stallMap</code> and the commodity Id from <code>commodMap</code>.
- * <code>priceIndex</code> should be the index of the price in the record
- * (the quantity will be <code>priceIndex + 1</code>).
- *
- * @param record the record with data to create the offer from
- * @param stallMap a map containing the ids of the various stalls
- * @param commodMap a map containing the ids of the various commodities
- * @param priceIndex the index of the price in the record
- */
- public Offer(ArrayList<String> record,
- LinkedHashMap<String,Integer> stallMap,
- HashMap<String,Integer> commodMap,
- int priceIndex) {
- Integer commodId = commodMap.get(record.get(0));
- if(commodId == null) {
- throw new IllegalArgumentException();
- }
- commodity = commodId.intValue();
- price = Integer.parseInt(record.get(priceIndex));
- String qty = record.get(priceIndex+1);
- quantity = parseQty(qty);
- shoppe = stallMap.get(record.get(1)).intValue();
- }
-
- /**
- * Returns a human-readable version of this offer, useful for debugging
- *
- * @return human-readable offer
- */
- public String toString() {
- return "[C:" + commodity + ",$" + price + ",Q:"
- + quantity + ",S:" + shoppe + "]";
- }
- }
-
- /**
- * An offer from a shoppe or stall to buy a certain quantity of a
- * commodity for a certain price. If placed in an ordered Set,
- * sorts by commodity index ascending, then by buy price
- * descending, and finally by stall id ascending.
- */
- class Buy extends Offer implements Comparable<Buy> {
- /**
- * Creates a new <code>Buy</code> offer from the given
- * <code>record</code> using the other parameters to determine
- * stall id and commodity id of the offer.
- *
- * @param record the record with data to create the offer from
- * @param stallMap a map containing the ids of the various stalls
- * @param commodMap a map containing the ids of the various commodities
- */
- public Buy(ArrayList<String> record,
- LinkedHashMap<String,Integer> stallMap,
- HashMap<String,Integer> commodMap) {
- super(record,stallMap,commodMap,2);
- }
-
- /**
- * Sorts by commodity index ascending, then price descending,
- * then stall id ascending.
- */
- public int compareTo(Buy buy) {
- // organize by: commodity index, price, stall index
- if(commodity == buy.commodity) {
- // organize by price, then by stall index
- if(price == buy.price) {
- // organize by stall index
- return shoppe>buy.shoppe ? 1 : -1;
- } else if(price > buy.price) {
- return -1;
- } else {
- return 1;
- }
- } else if(commodity > buy.commodity) {
- return 1;
- } else {
- return -1;
- }
- }
- }
-
- /**
- * An offer from a shoppe or stall to sell a certain quantity of
- * a commodity for a certain price. If placed in an ordered Set,
- * sorts by commodity index ascending, then by sell price
- * ascending, and finally by stall id ascending.
- */
- class Sell extends Offer implements Comparable<Sell> {
- /**
- * Creates a new <code>Sell</code> offer from the given
- * <code>record</code> using the other parameters to determine
- * stall id and commodity id of the offer.
- *
- * @param record the record with data to create the offer from
- * @param stallMap a map containing the ids of the various stalls
- * @param commodMap a map containing the ids of the various commodities
- */
- public Sell(ArrayList<String> record,
- LinkedHashMap<String,Integer> stallMap,
- HashMap<String,Integer> commodMap) {
- super(record,stallMap,commodMap,4);
- }
-
- /**
- * Sorts by commodity index ascending, then price ascending, then
- * stall id ascending.
- */
- public int compareTo(Sell sell) {
- // organize by: commodity index, price, stall index
- if(commodity == sell.commodity) {
- // organize by price, then by stall index
- if(price == sell.price) {
- // organize by stall index
- return shoppe>sell.shoppe ? 1 : -1;
- } else if(price > sell.price) {
- return 1;
- } else {
- return -1;
- }
- } else if(commodity > sell.commodity) {
- return 1;
- } else {
- return -1;
- }
- }
- }
- private void progressNote(final String s_in) throws Exception {
- new UIA() { public void body() {
- String arb = null;
- arb = arbitrageResult.getText();
- String s = s_in;
- if (arb != null && arb.length() != 0)
- s = "<html>" + arb + "<br>" + s;
- progmon.setNote(s);
- }}.exec("progressNote "+s_in);
- }
/*
* ENTRY POINT AND STARTUP
*
* Main thread and/or event thread
*/
- /*
- * Entry point. Read our preferences.
- */
+
+ public static void main(String[] args) {
+ new MarketUploader();
+ }
+
public MarketUploader() {
Preferences prefs = Preferences.userNodeForPackage(getClass());
/*
* We arrange to wait for the GUI to be initialised, then look at
- * every top-level window, and if it
+ * every top-level window to see if the Puzzle Pirates window turns up.
*/
public void run() {
debuglog("MarketUploader run()...");
String name = w.getAccessibleContext().getAccessibleName();
debuglog("MarketUploader checking toplevel "+name);
if (!name.equals("Puzzle Pirates"))
+ // Only if we're running alongside a Window named "Puzzle Pirates"
return;
debuglog("MarketUploader found toplevel, creating gui");
window = w;
frame.setVisible(true);
}
- /**
- * Set up the GUI, with its window and one-button
- * interface. Only initialize if we're running alongside
- * a Window named "Puzzle Pirates" though.
- */
private void createGUI() {
+ // Actually set up our GUI
on_ui_thread();
frame = new JFrame("Jarrg Uploader");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
findMarket = new JButton("Upload Market Data");
findMarket.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
+ // This is called when the user clicks "upload"
on_ui_thread();
+
uploadcounter++;
findMarket.setEnabled(false);
resultSummary.setText("");
error(whole_msg);
}
+ private void progressNote(final String s_in) throws Exception {
+ new UIA() { public void body() {
+ String arb = null;
+ arb = arbitrageResult.getText();
+ String s = s_in;
+ if (arb != null && arb.length() != 0)
+ s = "<html>" + arb + "<br>" + s;
+ progmon.setNote(s);
+ }}.exec("progressNote "+s_in);
+ }
private void setProgress(final int nv) throws Exception {
new UIA() { public void body() {
progmon.setProgress(nv);
return new Boolean(can);
}}.exec("checkCancelled").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)) {
- 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;
- // debuglog(islandName);
- sidePanel.removePropertyChangeListener(this);
- latch.countDown();
- }
- }
- };
-
- private void getIsland() {
- 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
-
- Accessible leagueTrackerContainer =
- descendNodes(window,new int[] {0,1,0,0,2,1});
- Accessible leagueTrackerItself =
- descendByClass(leagueTrackerContainer,
- "com.threerings.yohoho.sea.client.LeagueTracker");
- Accessible leagueTracker = descend(leagueTrackerItself, 1);
- try {
- islandName = ((JLabel)leagueTracker).getToolTipText();
- latch = null;
- } catch (NullPointerException e) {
- // evidently we're actually on an island
-
- islandName = null;
- AccessibleContext chatArea =
- descendNodes(window,new int[] {0,1,0,0,0,2,0,0,2})
- .getAccessibleContext();
- // attach the property change listener to the outer sunshine
- // panel if the "ahoy" tab is not active, otherwise attach it to
- // the scroll panel in the "ahoy" tab.
- if(!"com.threerings.piracy.client.AttentionListPanel".
- equals(descendNodes(window,new int[] {0,1,0,0,2,2,0})
- .getClass().getCanonicalName())) {
- sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2})
- .getAccessibleContext();
- } else {
- sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2,0,0,0})
- .getAccessibleContext();
- }
- sidePanel.addPropertyChangeListener(changeListener);
- latch = new java.util.concurrent.CountDownLatch(1);
- // make the Players Online ("/who") panel appear
- AccessibleEditableText chat = chatArea.getAccessibleEditableText();
- chat.setTextContents("/w");
- int c = chatArea.getAccessibleAction().getAccessibleActionCount();
- for(int i=0;i<c;i++) {
- if("notify-field-accept".equals(chatArea.getAccessibleAction()
- .getAccessibleActionDescription(i))) {
- chatArea.getAccessibleAction().doAccessibleAction(i);
- }
- }
- }
- }
-
- /**
- * Find the ocean name from the window title, and set global
- * oceanName variable
- */
- private void getOcean() {
- on_ui_thread();
- oceanName = null;
- AccessibleContext topwindow = window.getAccessibleContext();
- oceanName = topwindow.getAccessibleName()
- .replaceAll(".*on the (\\w+) ocean", "$1");
- }
- /**
- * Run the data collection process, and upload the results. This
- * is the method that calls most of the other worker methods for
- * the process. If an error occurs, the method will call the
- * error method and return early, freeing up the button to be
- * clicked again.
- *
- * @exception Exception if an error we didn't expect occured
+ /*
+ * ACTUAL DATA COLLECTION AND UPLOAD
*/
- private class YarrgTimestampFetcher extends Thread {
- public YarrgTimestampFetcher(int counter) {
- super("MarketUploader-YarrgTimestampFetcher-"+uploadcounter);
- }
- public String ts = null;
- public void run() {
- try {
- ts = getYarrgTimestamp();
- debuglog("(async) yarrg timestamp ready.");
- } catch(Exception e) {
- error("Error getting YARRG timestamp: "+e);
- }
- }
- };
private void runUpload(int counter) throws Exception {
+ // Runs the data collection process, and upload the results.
+ // In most cases of error, we call error() (which synchronously
+ // reports the error) and then simply return.
+
on_our_thread();
boolean doneyarrg = false, donepctb = false;
if(checkCancelled()) {
return;
}
- progressNote("PCTB: Waiting ...");
- debuglog("pctb finish...");
- donepctb = finishUpload(in);
- debuglog("pctb done.");
- }
- setProgress(99);
-
- String summary;
- if ((uploadToPCTB && !donepctb) ||
- (uploadToYarrg && !doneyarrg)) {
- summary= "trouble";
- } else if (unknownPCTBcommods != 0) {
- summary= "PCTB lacks "+unknownPCTBcommods+" commod(s)";
- } else if (donepctb || doneyarrg) {
- summary= "Done " + islandName;
- } else {
- summary= "uploaded nowhere!";
+ progressNote("PCTB: Waiting ...");
+ debuglog("pctb finish...");
+ donepctb = finishUpload(in);
+ debuglog("pctb done.");
+ }
+ setProgress(99);
+
+ String summary;
+ if ((uploadToPCTB && !donepctb) ||
+ (uploadToYarrg && !doneyarrg)) {
+ summary= "trouble";
+ } else if (unknownPCTBcommods != 0) {
+ summary= "PCTB lacks "+unknownPCTBcommods+" commod(s)";
+ } else if (donepctb || doneyarrg) {
+ summary= "Done " + islandName;
+ } else {
+ summary= "uploaded nowhere!";
+ }
+ final String summary_final = summary;
+ new UIX() { public void body() {
+ resultSummary.setText(summary_final);
+ }}.exec("resultSummary.setText");
+
+ debuglog("done.");
+ }
+
+ /*
+ * UPLOAD HELPER FUNCTIONS FOR EXTRACTING SPECIFIC UI DATA
+ */
+
+ private ArrayList<ArrayList<String>> getData(AccessibleTable table) {
+ // Gets the offer data out of the table and returns it as an ArrayList
+
+ 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>();
+ for (int j = 0; j < table.getAccessibleColumnCount(); j++) {
+ row.add(table.getAccessibleAt(i, j)
+ .getAccessibleContext().getAccessibleName());
+ }
+ data.add(row);
+ }
+ return data;
+ }
+
+ public AccessibleTable findMarketTable() {
+ // Return the table containing market data if it exists, otherwise null.
+ 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})
+ // 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();
+ // debuglog(table);
+ return table;
+ }
+
+ private boolean isDisplayAll() {
+ // Returns true iff the "Display:" menu on the commodities
+ // interface in YPP is set to "All"
+ 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});
+ }
+ String display = button.getAccessibleContext().getAccessibleName();
+ if(!display.equals("All")) {
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * FUNCTIONS AND CALLBACKS FOR FINDING ISLAND AND OCEAN
+ */
+
+ private void getOcean() {
+ // Finds the ocean name from the window title.
+ // Stores it in the global oceanName
+ on_ui_thread();
+
+ oceanName = null;
+ AccessibleContext topwindow = window.getAccessibleContext();
+ oceanName = topwindow.getAccessibleName()
+ .replaceAll(".*on the (\\w+) ocean", "$1");
+ }
+
+ private void getIsland() {
+ // Tries to find the island name. Either:
+ // (a) sets the islandName global
+ // or
+ // (b) sets latch to a new CountDownLatch, and arranges that
+ // at some point later, islandName will be set and the latch
+ // decremented to zero
+ 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
+
+ Accessible leagueTrackerContainer =
+ descendNodes(window,new int[] {0,1,0,0,2,1});
+ Accessible leagueTrackerItself =
+ descendByClass(leagueTrackerContainer,
+ "com.threerings.yohoho.sea.client.LeagueTracker");
+ Accessible leagueTracker = descend(leagueTrackerItself, 1);
+ try {
+ islandName = ((JLabel)leagueTracker).getToolTipText();
+ latch = null;
+ } catch (NullPointerException e) {
+ // evidently we're actually on an island
+
+ islandName = null;
+ AccessibleContext chatArea =
+ descendNodes(window,new int[] {0,1,0,0,0,2,0,0,2})
+ .getAccessibleContext();
+ // attach the property change listener to the outer sunshine
+ // panel if the "ahoy" tab is not active, otherwise attach it to
+ // the scroll panel in the "ahoy" tab.
+ if(!"com.threerings.piracy.client.AttentionListPanel".
+ equals(descendNodes(window,new int[] {0,1,0,0,2,2,0})
+ .getClass().getCanonicalName())) {
+ sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2})
+ .getAccessibleContext();
+ } else {
+ sidePanel = descendNodes(window,new int[] {0,1,0,0,2,2,0,0,0})
+ .getAccessibleContext();
+ }
+ sidePanel.addPropertyChangeListener(changeListener);
+ latch = new java.util.concurrent.CountDownLatch(1);
+ // make the Players Online ("/who") panel appear
+ AccessibleEditableText chat = chatArea.getAccessibleEditableText();
+ chat.setTextContents("/w");
+ int c = chatArea.getAccessibleAction().getAccessibleActionCount();
+ for(int i=0;i<c;i++) {
+ if("notify-field-accept".equals(chatArea.getAccessibleAction()
+ .getAccessibleActionDescription(i))) {
+ chatArea.getAccessibleAction().doAccessibleAction(i);
+ }
+ }
}
- final String summary_final = summary;
- new UIX() { public void body() {
- resultSummary.setText(summary_final);
- }}.exec("resultSummary.setText");
-
- debuglog("done.");
}
-
- /**
- * Get the offer data out of the table and cache it in an
- * <code>ArrayList</code>.
- *
- * @param table the <code>AccessibleTable</code> containing the market data
- * @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>();
- for (int j = 0; j < table.getAccessibleColumnCount(); j++) {
- row.add(table.getAccessibleAt(i, j)
- .getAccessibleContext().getAccessibleName());
+
+ private PropertyChangeListener changeListener = new PropertyChangeListener() {
+ // used by getIsland
+ public void propertyChange(PropertyChangeEvent e) {
+ on_ui_thread();
+ 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;
+ // debuglog(islandName);
+ sidePanel.removePropertyChangeListener(this);
+ latch.countDown();
}
- data.add(row);
- }
- return data;
- }
-
- /**
- * @return the table containing market data if it exists,
- * 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})
- // 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();
- // debuglog(table);
- return table;
- }
-
- /**
- * Utility method to descend through several levels of Accessible children
- * at once.
+ };
+
+ /*
+ * UTILITY FUNCTIONS FOR WALKING THE UI
*
- * @param parent the node on which to start the descent
- * @param path an array of ints, each int being the index of the next
- * accessible child to descend.
- * @return the <code>Accessible</code> reached by following the
- * descent path, or <code>null</code> if the desired path was
- * invalid.
+ * These functions all return null if the specified path or child
+ * was not found.
*/
+
private Accessible descendNodes(Accessible parent, int[] path) {
+ // Descends through several levels of Accessible children in one call.
+ // path[] is an array of ints, each int being the index into the array
+ // of children at a particular point, and thus selects the specific
+ // accessible child to descend to.
on_ui_thread();
+
for(int i=0;i<path.length;i++) {
if (null == (parent = descend(parent, path[i]))) return null;
}
return parent;
}
-
- /**
- * Descends one level to the specified child of the parent
- * <code>Accessible</code> "node".
- *
- * @param parent the node with children
- * @param childNum the index of the child of <code>parent</code> to return
- * @return the <code>childNum</code> child of <code>parent</code>
- * or <code>null</code> if the child is not found.
- */
+
private Accessible descend(Accessible parent, int childNum) {
+ // Descends one level to the specified child of the parent
+ // childNum is the index of the child within parent
on_ui_thread();
+
if (parent == null) return null;
int children = parent.getAccessibleContext().getAccessibleChildrenCount();
if (childNum >= children) {
return child;
}
- /**
- * Descends one level to the child which has the specified class.
- *
- * @param parent the node with children
- * @param classname the name of the class, as a string
- * @return the child or <code>null</code> if the child is not found.
- */
private Accessible descendByClass(Accessible parent, String classname) {
+ // Descends one level to the first child which has the specified class.
on_ui_thread();
+
if (parent == null) return null;
AccessibleContext ac = parent.getAccessibleContext();
int children = ac.getAccessibleChildrenCount();
debuglog("DESCEND CLASS "+classname+" NOT FOUND");
return null;
}
+
- public static void main(String[] args) {
- new MarketUploader();
- }
+ /*****************************************
+ * PCTB-SPECIFIC HELPER FUNCTIONS ETC. *
+ *****************************************/
/**
- * Returns true if the "Display:" menu on the commodities
- * interface in YPP is set to "All"
+ * An abstract market offer, entailing a commodity being bought or sold by
+ * a shoppe, for a certain price in a certain quantity. Not instantiable.
*
- * @return <code>true</code> if all commodities are displayed,
- * otherwise <code>false</code>
+ * @see Buy
+ * @see Sell
*/
- 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});
+ abstract class Offer {
+ public int commodity, price, quantity, shoppe;
+ /**
+ * Create an offer from <code>record</code>, determining the shoppe Id from
+ * <code>stallMap</code> and the commodity Id from <code>commodMap</code>.
+ * <code>priceIndex</code> should be the index of the price in the record
+ * (the quantity will be <code>priceIndex + 1</code>).
+ *
+ * @param record the record with data to create the offer from
+ * @param stallMap a map containing the ids of the various stalls
+ * @param commodMap a map containing the ids of the various commodities
+ * @param priceIndex the index of the price in the record
+ */
+ public Offer(ArrayList<String> record,
+ LinkedHashMap<String,Integer> stallMap,
+ HashMap<String,Integer> commodMap,
+ int priceIndex) {
+ Integer commodId = commodMap.get(record.get(0));
+ if(commodId == null) {
+ throw new IllegalArgumentException();
+ }
+ commodity = commodId.intValue();
+ price = Integer.parseInt(record.get(priceIndex));
+ String qty = record.get(priceIndex+1);
+ quantity = parseQty(qty);
+ shoppe = stallMap.get(record.get(1)).intValue();
}
- String display = button.getAccessibleContext().getAccessibleName();
- if(!display.equals("All")) {
- return false;
+
+ /**
+ * Returns a human-readable version of this offer, useful for debugging
+ *
+ * @return human-readable offer
+ */
+ public String toString() {
+ return "[C:" + commodity + ",$" + price + ",Q:"
+ + quantity + ",S:" + shoppe + "]";
+ }
+ }
+
+ /**
+ * An offer from a shoppe or stall to buy a certain quantity of a
+ * commodity for a certain price. If placed in an ordered Set,
+ * sorts by commodity index ascending, then by buy price
+ * descending, and finally by stall id ascending.
+ */
+ class Buy extends Offer implements Comparable<Buy> {
+ /**
+ * Creates a new <code>Buy</code> offer from the given
+ * <code>record</code> using the other parameters to determine
+ * stall id and commodity id of the offer.
+ *
+ * @param record the record with data to create the offer from
+ * @param stallMap a map containing the ids of the various stalls
+ * @param commodMap a map containing the ids of the various commodities
+ */
+ public Buy(ArrayList<String> record,
+ LinkedHashMap<String,Integer> stallMap,
+ HashMap<String,Integer> commodMap) {
+ super(record,stallMap,commodMap,2);
+ }
+
+ /**
+ * Sorts by commodity index ascending, then price descending,
+ * then stall id ascending.
+ */
+ public int compareTo(Buy buy) {
+ // organize by: commodity index, price, stall index
+ if(commodity == buy.commodity) {
+ // organize by price, then by stall index
+ if(price == buy.price) {
+ // organize by stall index
+ return shoppe>buy.shoppe ? 1 : -1;
+ } else if(price > buy.price) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else if(commodity > buy.commodity) {
+ return 1;
+ } else {
+ return -1;
+ }
}
- return true;
}
+ /**
+ * An offer from a shoppe or stall to sell a certain quantity of
+ * a commodity for a certain price. If placed in an ordered Set,
+ * sorts by commodity index ascending, then by sell price
+ * ascending, and finally by stall id ascending.
+ */
+ class Sell extends Offer implements Comparable<Sell> {
+ /**
+ * Creates a new <code>Sell</code> offer from the given
+ * <code>record</code> using the other parameters to determine
+ * stall id and commodity id of the offer.
+ *
+ * @param record the record with data to create the offer from
+ * @param stallMap a map containing the ids of the various stalls
+ * @param commodMap a map containing the ids of the various commodities
+ */
+ public Sell(ArrayList<String> record,
+ LinkedHashMap<String,Integer> stallMap,
+ HashMap<String,Integer> commodMap) {
+ super(record,stallMap,commodMap,4);
+ }
+
+ /**
+ * Sorts by commodity index ascending, then price ascending, then
+ * stall id ascending.
+ */
+ public int compareTo(Sell sell) {
+ // organize by: commodity index, price, stall index
+ if(commodity == sell.commodity) {
+ // organize by price, then by stall index
+ if(price == sell.price) {
+ // organize by stall index
+ return shoppe>sell.shoppe ? 1 : -1;
+ } else if(price > sell.price) {
+ return 1;
+ } else {
+ return -1;
+ }
+ } else if(commodity > sell.commodity) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ }
+
/**
* Gets the list of commodities and their associated commodity ids.
*
}
}
+
+ /*****************************************
+ * YARRG-SPECIFIC HELPER FUNCTIONS ETC. *
+ *****************************************/
+
private InputStream post_for_yarrg(ClientHttpRequest http)
throws IOException {
on_our_thread();
return http.resultstream();
}
+ private class YarrgTimestampFetcher extends Thread {
+ public YarrgTimestampFetcher(int counter) {
+ super("MarketUploader-YarrgTimestampFetcher-"+uploadcounter);
+ }
+ public String ts = null;
+ public void run() {
+ try {
+ ts = getYarrgTimestamp();
+ debuglog("(async) yarrg timestamp ready.");
+ } catch(Exception e) {
+ error("Error getting YARRG timestamp: "+e);
+ }
+ }
+ };
+
private String getYarrgTimestamp() throws IOException {
ClientHttpRequest http = new ClientHttpRequest (YARRG_URL);
http.setParameter("clientname", YARRG_CLIENTNAME);
return profit;
}
+ /*****************************************
+ * ARBITRAGE *
+ *****************************************/
+
private class arbitrageOfferComparator implements Comparator {
public int compare(Object o1, Object o2) {
int p1 = ((int[])o1)[0];