Abstracting out PlayerStatus to a separate class and adding the concept of cash. Also adds GWT checkstyle to the project and cleans up checkstyle errors.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7565 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/.project b/bikeshed/.project
index f9e52b1..ef2fe07 100644
--- a/bikeshed/.project
+++ b/bikeshed/.project
@@ -30,11 +30,17 @@
<arguments>
</arguments>
</buildCommand>
+ <buildCommand>
+ <name>com.atlassw.tools.eclipse.checkstyle.CheckstyleBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>com.google.appengine.eclipse.core.gaeNature</nature>
<nature>com.google.gwt.eclipse.core.gwtNature</nature>
<nature>com.google.gdt.eclipse.core.webAppNature</nature>
+ <nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature>
</natures>
</projectDescription>
diff --git a/bikeshed/src/com/google/gwt/cells/client/ButtonCell.java b/bikeshed/src/com/google/gwt/cells/client/ButtonCell.java
index 4543b51..717fd4c 100644
--- a/bikeshed/src/com/google/gwt/cells/client/ButtonCell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/ButtonCell.java
@@ -18,18 +18,12 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+/**
+ * A {@link Cell} used to render a button.
+ */
public class ButtonCell extends Cell<String> {
@Override
- public void render(String data, StringBuilder sb) {
- sb.append("<button>");
- if (data != null) {
- sb.append(data);
- }
- sb.append("</button>");
- }
-
- @Override
public void onBrowserEvent(Element parent, String value, NativeEvent event,
Mutator<String, String> mutator) {
if (mutator == null) {
@@ -40,4 +34,13 @@
mutator.mutate(value, null);
}
}
+
+ @Override
+ public void render(String data, StringBuilder sb) {
+ sb.append("<button>");
+ if (data != null) {
+ sb.append(data);
+ }
+ sb.append("</button>");
+ }
}
diff --git a/bikeshed/src/com/google/gwt/cells/client/Cell.java b/bikeshed/src/com/google/gwt/cells/client/Cell.java
index e92dbe1..401a622 100644
--- a/bikeshed/src/com/google/gwt/cells/client/Cell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/Cell.java
@@ -18,6 +18,11 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+/**
+ * A light weight representation of a renderable object.
+ *
+ * @param <C> the type that this Cell represents
+ */
public abstract class Cell<C> {
/**
diff --git a/bikeshed/src/com/google/gwt/cells/client/CheckboxCell.java b/bikeshed/src/com/google/gwt/cells/client/CheckboxCell.java
index 594a995..9800eec 100644
--- a/bikeshed/src/com/google/gwt/cells/client/CheckboxCell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/CheckboxCell.java
@@ -19,18 +19,12 @@
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
+/**
+ * A {@link Cell} used to render a checkbox.
+ */
public class CheckboxCell extends Cell<Boolean> {
@Override
- public void render(Boolean data, StringBuilder sb) {
- sb.append("<input type=\"checkbox\"");
- if (data == Boolean.TRUE) {
- sb.append(" checked");
- }
- sb.append("/>");
- }
-
- @Override
public void onBrowserEvent(Element parent, Boolean value, NativeEvent event,
Mutator<Boolean, Boolean> mutator) {
if (mutator == null) {
@@ -42,4 +36,13 @@
mutator.mutate(value, input.isChecked());
}
}
+
+ @Override
+ public void render(Boolean data, StringBuilder sb) {
+ sb.append("<input type=\"checkbox\"");
+ if (data == Boolean.TRUE) {
+ sb.append(" checked");
+ }
+ sb.append("/>");
+ }
}
diff --git a/bikeshed/src/com/google/gwt/cells/client/CurrencyCell.java b/bikeshed/src/com/google/gwt/cells/client/CurrencyCell.java
index a09fbb4..9a7a1fa 100644
--- a/bikeshed/src/com/google/gwt/cells/client/CurrencyCell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/CurrencyCell.java
@@ -15,13 +15,16 @@
*/
package com.google.gwt.cells.client;
+/**
+ * A {@link Cell} used to render currency.
+ */
public class CurrencyCell extends Cell<Integer> {
@Override
public void render(Integer price, StringBuilder sb) {
int dollars = price / 100;
int cents = price % 100;
-
+
sb.append("$ ");
sb.append(dollars);
sb.append('.');
diff --git a/bikeshed/src/com/google/gwt/cells/client/Mutator.java b/bikeshed/src/com/google/gwt/cells/client/Mutator.java
index 4d5584d..6b20b5e 100644
--- a/bikeshed/src/com/google/gwt/cells/client/Mutator.java
+++ b/bikeshed/src/com/google/gwt/cells/client/Mutator.java
@@ -15,6 +15,12 @@
*/
package com.google.gwt.cells.client;
+/**
+ * A mutator can be added to a Cell to mutate data.
+ *
+ * @param <T> the data type that will be modified
+ * @param <C> the data type of the cell
+ */
public interface Mutator<T, C> {
void mutate(T object, C after);
}
diff --git a/bikeshed/src/com/google/gwt/cells/client/TextCell.java b/bikeshed/src/com/google/gwt/cells/client/TextCell.java
index 4b29f02..5dd5ebe 100644
--- a/bikeshed/src/com/google/gwt/cells/client/TextCell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/TextCell.java
@@ -15,6 +15,9 @@
*/
package com.google.gwt.cells.client;
+/**
+ * A {@link Cell} used to render text.
+ */
public class TextCell extends Cell<String> {
@Override
diff --git a/bikeshed/src/com/google/gwt/cells/client/TextInputCell.java b/bikeshed/src/com/google/gwt/cells/client/TextInputCell.java
index 318fd6d..bafc0ea 100644
--- a/bikeshed/src/com/google/gwt/cells/client/TextInputCell.java
+++ b/bikeshed/src/com/google/gwt/cells/client/TextInputCell.java
@@ -19,18 +19,12 @@
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
+/**
+ * A {@link Cell} used to render a text input.
+ */
public class TextInputCell extends Cell<String> {
@Override
- public void render(String data, StringBuilder sb) {
- sb.append("<input type='text'");
- if (data != null) {
- sb.append(" value='" + data + "'");
- }
- sb.append("></input>");
- }
-
- @Override
public void onBrowserEvent(Element parent, String value, NativeEvent event,
Mutator<String, String> mutator) {
if (mutator == null) {
@@ -42,4 +36,13 @@
mutator.mutate(value, input.getValue());
}
}
+
+ @Override
+ public void render(String data, StringBuilder sb) {
+ sb.append("<input type='text'");
+ if (data != null) {
+ sb.append(" value='" + data + "'");
+ }
+ sb.append("></input>");
+ }
}
diff --git a/bikeshed/src/com/google/gwt/list/client/Column.java b/bikeshed/src/com/google/gwt/list/client/Column.java
index 2c41412..e2aec50 100644
--- a/bikeshed/src/com/google/gwt/list/client/Column.java
+++ b/bikeshed/src/com/google/gwt/list/client/Column.java
@@ -20,6 +20,12 @@
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
+/**
+ * A representation of a column in a table.
+ *
+ * @param <T> the row type
+ * @param <C> the column type
+ */
public abstract class Column<T, C> {
private final Cell<C> cell;
private Mutator<T, C> mutator;
@@ -44,9 +50,10 @@
this.mutator = mutator;
}
- protected abstract C getValue(T object);
-
protected Cell<C> getCell() {
return cell;
}
+
+ protected abstract C getValue(T object);
+
}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/list/client/PagingTableListView.java b/bikeshed/src/com/google/gwt/list/client/PagingTableListView.java
index 0c30243..a4d1811 100644
--- a/bikeshed/src/com/google/gwt/list/client/PagingTableListView.java
+++ b/bikeshed/src/com/google/gwt/list/client/PagingTableListView.java
@@ -38,12 +38,17 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * A list view that supports paging and columns.
+ *
+ * @param <T> the data type of each row.
+ */
public class PagingTableListView<T> extends Widget {
+ protected int curPage;
private int pageSize;
private int numPages;
private ListRegistration listReg;
- protected int curPage;
private int totalSize;
private List<Column<T, ?>> columns = new ArrayList<Column<T, ?>>();
private ArrayList<T> data = new ArrayList<T>();
@@ -84,6 +89,19 @@
setPage(curPage); // TODO: better way to refresh?
}
+ /**
+ * Get the current page.
+ *
+ * @return the current page
+ */
+ public int getPage() {
+ return curPage;
+ }
+
+ public void nextPage() {
+ setPage(curPage + 1);
+ }
+
@Override
public void onBrowserEvent(Event event) {
EventTarget target = event.getEventTarget();
@@ -106,17 +124,19 @@
column.onBrowserEvent(elem, value, event);
} else if (row == pageSize) {
if (col == 0) {
- prevButton.onBrowserEvent(elem, null, event, new Mutator<String,String>() {
- public void mutate(String object, String after) {
- previousPage();
- }
- });
+ prevButton.onBrowserEvent(elem, null, event,
+ new Mutator<String, String>() {
+ public void mutate(String object, String after) {
+ previousPage();
+ }
+ });
} else if (col == 2) {
- nextButton.onBrowserEvent(elem, null, event, new Mutator<String,String>() {
- public void mutate(String object, String after) {
- nextPage();
- }
- });
+ nextButton.onBrowserEvent(elem, null, event,
+ new Mutator<String, String>() {
+ public void mutate(String object, String after) {
+ nextPage();
+ }
+ });
}
}
break;
@@ -127,6 +147,45 @@
}
}
+ public void previousPage() {
+ setPage(curPage - 1);
+ }
+
+ /**
+ * Set the current visible page.
+ *
+ * @param page the page index
+ */
+ public void setPage(int page) {
+ int newPage = Math.min(page, numPages - 1);
+ newPage = Math.max(0, newPage);
+
+ // Update the text showing the page number.
+ updatePageText(newPage);
+
+ // Early exit if we are already on the right page.
+ if (curPage != newPage) {
+ curPage = newPage;
+ listReg.setRangeOfInterest(curPage * pageSize, pageSize);
+ }
+
+ updateRowVisibility();
+ }
+
+ /**
+ * Set the number of rows per page.
+ *
+ * @param pageSize the page size
+ */
+ public void setPageSize(int pageSize) {
+ if (this.pageSize == pageSize) {
+ return;
+ }
+ this.pageSize = pageSize;
+ curPage = -1;
+ setPage(curPage);
+ }
+
protected void render(int start, int length, List<T> values) {
TableElement table = getElement().cast();
int numCols = columns.size();
@@ -153,70 +212,52 @@
}
}
- /**
- * Get the current page.
- *
- * @return the current page
- */
- public int getPage() {
- return curPage;
- }
-
- public void nextPage() {
- setPage(curPage + 1);
- }
-
- public void previousPage() {
- setPage(curPage - 1);
- }
+ private void createRows() {
+ TableElement table = getElement().cast();
+ int numCols = columns.size();
- /**
- * Set the current visible page.
- *
- * @param page the page index
- */
- public void setPage(int page) {
- int newPage = Math.min(page, numPages - 1);
- newPage = Math.max(0, newPage);
-
- // Update the text showing the page number.
- updatePageText(newPage);
-
- // Early exit if we are already on the right page.
- if (curPage != newPage) {
- curPage = newPage;
- listReg.setRangeOfInterest(curPage * pageSize, pageSize);
+ // TODO - only delete as needed
+ int numRows = table.getRows().getLength();
+ while (numRows-- > 0) {
+ table.deleteRow(0);
}
- updateRowVisibility();
- }
-
- private void updateRowVisibility() {
- int visible = Math.min(pageSize, totalSize - curPage * pageSize);
-
- TableElement table = getElement().cast();
for (int r = 0; r < pageSize; ++r) {
- Style rowStyle = table.getRows().getItem(r).getStyle();
- if (r < visible) {
- rowStyle.clearDisplay();
- } else {
- rowStyle.setDisplay(Display.NONE);
+ TableRowElement row = table.insertRow(0);
+ row.setClassName("pagingTableListView "
+ + ((r & 0x1) == 0 ? "evenRow" : "oddRow"));
+
+ // TODO: use cloneNode() to make this even faster.
+ for (int c = 0; c < numCols; ++c) {
+ row.insertCell(c);
}
}
- }
- /**
- * Set the number of rows per page.
- *
- * @param pageSize the page size
- */
- public void setPageSize(int pageSize) {
- if (this.pageSize == pageSize) {
- return;
+ // Add the final row containing paging buttons
+ TableRowElement pageRow = table.insertRow(pageSize);
+ pageRow.insertCell(0);
+ pageRow.insertCell(1);
+ pageRow.insertCell(2);
+
+ StringBuilder sb;
+
+ sb = new StringBuilder();
+ prevButton.render("Previous", sb);
+ pageRow.getCells().getItem(0).setInnerHTML(sb.toString());
+
+ pageRow.getCells().getItem(1).setAttribute("colspan", "" + (numCols - 2));
+ pageRow.getCells().getItem(1).setAttribute("align", "center");
+
+ sb = new StringBuilder();
+ nextButton.render("Next", sb);
+ pageRow.getCells().getItem(2).setInnerHTML(sb.toString());
+ pageRow.getCells().getItem(2).setAttribute("align", "right");
+
+ // Make room for the data cache
+ data.ensureCapacity(pageSize);
+ while (data.size() < pageSize) {
+ data.add(null);
}
- this.pageSize = pageSize;
- curPage = -1;
- setPage(curPage);
}
/**
@@ -231,50 +272,17 @@
rows.getItem(rows.getLength() - 1).getCells().getItem(1).setInnerText(text);
}
- private void createRows() {
+ private void updateRowVisibility() {
+ int visible = Math.min(pageSize, totalSize - curPage * pageSize);
+
TableElement table = getElement().cast();
- int numCols = columns.size();
-
- // TODO - only delete as needed
- int numRows = table.getRows().getLength();
- while (numRows-- > 0) {
- table.deleteRow(0);
- }
-
for (int r = 0; r < pageSize; ++r) {
- TableRowElement row = table.insertRow(0);
- row.setClassName("pagingTableListView " + ((r & 0x1) == 0 ? "evenRow" : "oddRow"));
-
- // TODO: use cloneNode() to make this even faster.
- for (int c = 0; c < numCols; ++c) {
- row.insertCell(c);
+ Style rowStyle = table.getRows().getItem(r).getStyle();
+ if (r < visible) {
+ rowStyle.clearDisplay();
+ } else {
+ rowStyle.setDisplay(Display.NONE);
}
}
-
- // Add the final row containing paging buttons
- TableRowElement pageRow = table.insertRow(pageSize);
- pageRow.insertCell(0);
- pageRow.insertCell(1);
- pageRow.insertCell(2);
-
- StringBuilder sb;
-
- sb = new StringBuilder();
- prevButton.render("Previous", sb);
- pageRow.getCells().getItem(0).setInnerHTML(sb.toString());
-
- pageRow.getCells().getItem(1).setAttribute("colspan", "" + (numCols - 2));
- pageRow.getCells().getItem(1).setAttribute("align", "center");
-
- sb = new StringBuilder();
- nextButton.render("Next", sb);
- pageRow.getCells().getItem(2).setInnerHTML(sb.toString());
- pageRow.getCells().getItem(2).setAttribute("align", "right");
-
- // Make room for the data cache
- data.ensureCapacity(pageSize);
- while (data.size() < pageSize) {
- data.add(null);
- }
}
}
diff --git a/bikeshed/src/com/google/gwt/list/client/SimpleCellList.java b/bikeshed/src/com/google/gwt/list/client/SimpleCellList.java
index 35ee5f4..b9ba606 100644
--- a/bikeshed/src/com/google/gwt/list/client/SimpleCellList.java
+++ b/bikeshed/src/com/google/gwt/list/client/SimpleCellList.java
@@ -30,6 +30,11 @@
import java.util.ArrayList;
import java.util.List;
+/**
+ * A single column list of cells.
+ *
+ * @param <T> the data type of list items
+ */
public class SimpleCellList<T> extends Widget {
private final Cell<T> cell;
@@ -81,16 +86,21 @@
@Override
public void onBrowserEvent(Event event) {
Element target = event.getEventTarget().cast();
- String __idx = "";
- while ((target != null) && ((__idx = target.getAttribute("__idx")).length() == 0)) {
+ String idxString = "";
+ while ((target != null)
+ && ((idxString = target.getAttribute("__idx")).length() == 0)) {
target = target.getParentElement();
}
- if (__idx.length() > 0) {
- int idx = Integer.parseInt(__idx);
+ if (idxString.length() > 0) {
+ int idx = Integer.parseInt(idxString);
cell.onBrowserEvent(target, data.get(idx), event, mutator);
}
}
+ public void setMutator(Mutator<T, T> mutator) {
+ this.mutator = mutator;
+ }
+
private void gc(int size) {
// Remove unused children if the size shrinks.
int childCount = getElement().getChildCount();
@@ -132,8 +142,4 @@
}
}
}
-
- public void setMutator(Mutator<T, T> mutator) {
- this.mutator = mutator;
- }
}
diff --git a/bikeshed/src/com/google/gwt/list/shared/AbstractListModel.java b/bikeshed/src/com/google/gwt/list/shared/AbstractListModel.java
index c7f5b24..7daf7c1 100644
--- a/bikeshed/src/com/google/gwt/list/shared/AbstractListModel.java
+++ b/bikeshed/src/com/google/gwt/list/shared/AbstractListModel.java
@@ -29,7 +29,7 @@
/**
* The range of interest for a single handler.
*/
- static class DefaultRange implements Range, Serializable {
+ public static class DefaultRange implements Range, Serializable {
private int start;
private int length;
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java
index 98cc9ed..317ae2f 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java
@@ -17,6 +17,9 @@
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.sample.datawidgets.shared.StockQuote;
import com.google.gwt.sample.datawidgets.shared.Transaction;
import com.google.gwt.user.client.Command;
@@ -69,6 +72,22 @@
layout.setHTML(2, 0, "<b>Price:</b>");
layout.setHTML(3, 0, "<b>Quantity:</b>");
layout.setWidget(3, 1, quantityBox);
+ layout.setHTML(4, 0, "<b>Total:</b>");
+ layout.setHTML(5, 0, "<b>Available:</b>");
+
+ // Update total price when the quantity changes.
+ quantityBox.addKeyUpHandler(new KeyUpHandler() {
+ public void onKeyUp(KeyUpEvent event) {
+ try {
+ int quantity = Integer.parseInt(quantityBox.getText());
+ double totalPrice = quantity * quote.getPrice() / 100.0;
+ layout.setText(4, 1, NumberFormat.getCurrencyFormat("USD").format(
+ totalPrice));
+ } catch (NumberFormatException e) {
+ layout.setText(4, 1, "Invalid quantity");
+ }
+ }
+ });
// Buy Button.
opButton = new Button("", new ClickHandler() {
@@ -82,7 +101,7 @@
}
}
});
- layout.setWidget(4, 0, opButton);
+ layout.setWidget(6, 0, opButton);
// Cancel Button.
Button cancelButton = new Button("Cancel", new ClickHandler() {
@@ -90,7 +109,7 @@
hide();
}
});
- layout.setWidget(4, 1, cancelButton);
+ layout.setWidget(6, 1, cancelButton);
}
/**
@@ -103,6 +122,16 @@
}
/**
+ * Set the available cash.
+ *
+ * @param cash the available cash
+ */
+ public void setAvailableCash(double cash) {
+ // TODO: Bind the available cash field.
+ layout.setText(5, 1, NumberFormat.getCurrencyFormat("USD").format(cash));
+ }
+
+ /**
* Set the current {@link StockQuote}.
*
* @param quote the stock quote to buy
@@ -112,9 +141,10 @@
this.quote = quote;
String op = isBuying ? "Buy" : "Sell";
setText(op + " " + quote.getTicker() + " (" + quote.getName() + ")");
- layout.setHTML(0, 1, quote.getTicker());
- layout.setHTML(1, 1, quote.getName());
- layout.setHTML(2, 1, quote.getDisplayPrice());
+ layout.setText(0, 1, quote.getTicker());
+ layout.setText(1, 1, quote.getName());
+ layout.setText(2, 1, quote.getDisplayPrice());
+ layout.setText(4, 1, NumberFormat.getCurrencyFormat("USD").format(0.0));
quantityBox.setText("0");
opButton.setText(op);
this.isBuying = isBuying;
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
index 4ceb9dd..a1e15c9 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
@@ -26,6 +26,7 @@
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.list.client.Column;
import com.google.gwt.list.client.PagingTableListView;
import com.google.gwt.list.shared.AsyncListModel;
@@ -40,6 +41,7 @@
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
@@ -103,6 +105,7 @@
};
private final TextBox queryField = new TextBox();
+ private final Label cashLabel = new Label();
private Column<StockQuote, String> tickerColumn = new Column<StockQuote, String>(
new TextCell()) {
@@ -225,6 +228,12 @@
}
});
+ // Add components to the page.
+ HorizontalPanel hPanel = new HorizontalPanel();
+ hPanel.add(new HTML("<b>Available cash:</b>"));
+ hPanel.add(cashLabel);
+ RootPanel.get().add(hPanel);
+
RootPanel.get().add(resultsTable);
RootPanel.get().add(new HTML("<hr>"));
RootPanel.get().add(favoritesTable);
@@ -240,7 +249,7 @@
}
/**
- * Set or unset a ticker symbol as a 'favorite.'
+ * Set or unset a ticker symbol as a 'favorite'.
*
* @param ticker the ticker symbol
* @param favorite if true, make the stock a favorite
@@ -270,6 +279,33 @@
}
/**
+ * Process the {@link StockResponse} from the server.
+ *
+ * @param response the stock response
+ */
+ private void processStockResponse(StockResponse response) {
+ // Update the search list.
+ StockQuoteList searchResults = response.getSearchResults();
+ searchListModel.updateDataSize(response.getNumSearchResults(), true);
+ searchListModel.updateViewData(searchResults.getStartIndex(),
+ searchResults.size(), searchResults);
+
+ // Update the favorites list.
+ StockQuoteList favorites = response.getFavorites();
+ favoritesListModel.updateDataSize(response.getNumFavorites(), true);
+ favoritesListModel.updateViewData(favorites.getStartIndex(),
+ favorites.size(), favorites);
+
+ // Update available cash.
+ double cash = response.getCash() / 100.0;
+ cashLabel.setText(NumberFormat.getCurrencyFormat("USD").format(cash));
+ buySellPopup.setAvailableCash(cash);
+
+ // Restart the update timer.
+ updateTimer.schedule(UPDATE_DELAY);
+ }
+
+ /**
* Request data from the server using the last query string.
*/
private void update() {
@@ -288,24 +324,18 @@
favoritesRanges[0]);
dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
- Window.alert("ERROR: " + caught.getMessage());
- updateTimer.schedule(UPDATE_DELAY);
+ String message = caught.getMessage();
+ if (message.contains("Not logged in")) {
+ // Force the user to login.
+ Window.Location.reload();
+ } else {
+ Window.alert("ERROR: " + caught.getMessage());
+ updateTimer.schedule(UPDATE_DELAY);
+ }
}
public void onSuccess(StockResponse result) {
- StockQuoteList searchResults = result.getSearchResults();
-
- searchListModel.updateDataSize(result.getNumSearchResults(), true);
- searchListModel.updateViewData(searchResults.getStartIndex(),
- searchResults.size(), searchResults);
-
- StockQuoteList favorites = result.getFavorites();
-
- favoritesListModel.updateDataSize(result.getNumFavorites(), true);
- favoritesListModel.updateViewData(favorites.getStartIndex(),
- favorites.size(), favorites);
-
- updateTimer.schedule(UPDATE_DELAY);
+ processStockResponse(result);
}
});
}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockQuoteCell.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockQuoteCell.java
index 813d2dc..2b9e983 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockQuoteCell.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockQuoteCell.java
@@ -18,6 +18,9 @@
import com.google.gwt.cells.client.Cell;
import com.google.gwt.sample.datawidgets.shared.StockQuote;
+/**
+ * A cell that represents a {@link StockQuote}.
+ */
public class StockQuoteCell extends Cell<StockQuote> {
@Override
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/server/PlayerStatus.java b/bikeshed/src/com/google/gwt/sample/datawidgets/server/PlayerStatus.java
new file mode 100644
index 0000000..41da1e2
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/server/PlayerStatus.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.sample.datawidgets.server;
+
+import java.util.HashMap;
+import java.util.TreeSet;
+
+/**
+ * Game state for a single player.
+ */
+public class PlayerStatus {
+
+ /**
+ * The initial amount of cash that the player starts with, in cents.
+ */
+ private static final int INITIAL_CASH = 10000 * 100;
+
+ /**
+ * An impossible stock ticker.
+ */
+ private static final String IMPOSSIBLE_TICKER_SYMBOL = "XXXXXXXXXX";
+
+ /**
+ * The amount of cash that the player has.
+ */
+ private int cash = INITIAL_CASH;
+
+ /**
+ * This players favorite stocks.
+ */
+ private TreeSet<String> favorites = new TreeSet<String>();
+
+ /**
+ * The query used to retrieve favorites.
+ */
+ private String favoritesQuery;
+
+ /**
+ * The number of shares owned for each symbol.
+ */
+ private HashMap<String, Integer> sharesOwnedBySymbol = new HashMap<String, Integer>();
+
+ public PlayerStatus() {
+ generateFavoritesQuery();
+ }
+
+ /**
+ * Add a stock to the favorites list.
+ *
+ * @param ticker the stock ticker
+ */
+ public void addFavorite(String ticker) {
+ favorites.add(ticker);
+ generateFavoritesQuery();
+ }
+
+ /**
+ * Purchase stock.
+ *
+ * @param ticker the stock ticker
+ * @param quantity the number of shares to buy
+ * @param price the price of the stock
+ * @throws IllegalArgumentException if the stock cannot be purchased
+ */
+ public void buy(String ticker, int quantity, int price)
+ throws IllegalArgumentException {
+ // Verify that the player can afford the stock.
+ int totalPrice = price * quantity;
+ if (cash < totalPrice) {
+ throw new IllegalArgumentException("You cannot afford that much stock");
+ }
+
+ // Update the number of shares owned.
+ int current = getSharesOwned(ticker);
+ cash -= totalPrice;
+ current += quantity;
+ sharesOwnedBySymbol.put(ticker, current);
+
+ // Add this stock to the favorites list.
+ addFavorite(ticker);
+ }
+
+ /**
+ * Get the player's current cash amount.
+ *
+ * @return the cash amount
+ */
+ public int getCash() {
+ return cash;
+ }
+
+ /**
+ * Get this players favorite query.
+ *
+ * @return the query
+ */
+ public String getFavoritesQuery() {
+ return favoritesQuery;
+ }
+
+ /**
+ * Get the number of shares owned for a given stock.
+ *
+ * @param ticker the stock ticker
+ * @return the number of shares owned
+ */
+ public int getSharesOwned(String ticker) {
+ Integer current = sharesOwnedBySymbol.get(ticker);
+ return current == null ? 0 : current;
+ }
+
+ /**
+ * Check if the stock ticker is in the favorites list.
+ *
+ * @param ticker the stock sticker
+ * @return true if a favorite, false if not
+ */
+ public boolean isFavorite(String ticker) {
+ return favorites.contains(ticker);
+ }
+
+ /**
+ * Remove a stock from the favorites list.
+ *
+ * @param ticker the stock ticker
+ */
+ public void removeFavorite(String ticker) {
+ favorites.remove(ticker);
+ generateFavoritesQuery();
+ }
+
+ /**
+ * Sell stock.
+ *
+ * @param ticker the stock ticker
+ * @param quantity the number of shares to sell
+ * @param price the price of the stock
+ * @throws IllegalArgumentException if the stock cannot be sold
+ */
+ public void sell(String ticker, int quantity, int price)
+ throws IllegalArgumentException {
+ // Verify that the player has enough stock to sell.
+ int current = sharesOwnedBySymbol.get(ticker);
+ if (quantity > current) {
+ throw new IllegalArgumentException(
+ "You cannot sell more stock than you own");
+ }
+
+ // Perform the transaction.
+ cash += quantity * price;
+ current -= quantity;
+ sharesOwnedBySymbol.put(ticker, current);
+ }
+
+ /**
+ * Regenerate the favorites query.
+ */
+ private void generateFavoritesQuery() {
+ StringBuilder sb = new StringBuilder(IMPOSSIBLE_TICKER_SYMBOL);
+ for (String ticker : favorites) {
+ sb.append('|');
+ sb.append(ticker);
+ }
+ favoritesQuery = sb.toString();
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
index bf64eec..810cec1 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
@@ -15,7 +15,11 @@
*/
package com.google.gwt.sample.datawidgets.server;
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
import com.google.gwt.list.shared.Range;
+import com.google.gwt.list.shared.AbstractListModel.DefaultRange;
import com.google.gwt.sample.datawidgets.client.StockService;
import com.google.gwt.sample.datawidgets.shared.StockQuote;
import com.google.gwt.sample.datawidgets.shared.StockQuoteList;
@@ -47,8 +51,9 @@
public class StockServiceImpl extends RemoteServiceServlet implements
StockService {
- private static final String IMPOSSIBLE_TICKER_SYMBOL = "XXXXXXXXXX";
-
+ /**
+ * The result of a query to the remote service that provides stock quotes.
+ */
private class Result {
int numRows;
StockQuoteList quotes;
@@ -76,15 +81,13 @@
}
}
- private TreeSet<String> favorites = new TreeSet<String>();
-
- private String favoritesQuery = IMPOSSIBLE_TICKER_SYMBOL;
-
- private HashMap<String, Integer> sharesOwnedBySymbol = new HashMap<String, Integer>();
+ /**
+ * A mapping of usernames to {@link PlayerStatus}.
+ */
+ private Map<String, PlayerStatus> players = new HashMap<String, PlayerStatus>();
public void addFavorite(String ticker) {
- favorites.add(ticker);
- generateFavoritesQuery();
+ ensurePlayer().addFavorite(ticker);
}
public StockResponse getStockQuotes(StockRequest request)
@@ -97,50 +100,59 @@
Range searchRange = request.getSearchRange();
Range favoritesRange = request.getFavoritesRange();
+ PlayerStatus player = ensurePlayer();
Result searchResults = query(query, searchRange);
- Result favorites = query(favoritesQuery, favoritesRange);
+ Result favorites = query(player.getFavoritesQuery(), favoritesRange);
return new StockResponse(searchResults.quotes, favorites.quotes,
- searchResults.numRows, favorites.numRows);
+ searchResults.numRows, favorites.numRows, player.getCash());
}
public void removeFavorite(String ticker) {
- favorites.remove(ticker);
- generateFavoritesQuery();
+ ensurePlayer().removeFavorite(ticker);
}
public Transaction transact(Transaction transaction)
throws IllegalArgumentException {
- // TODO: Check that the stock exists.
+ // Get the current stock price.
String ticker = transaction.getTicker();
- Integer current = sharesOwnedBySymbol.get(ticker);
- if (current == null) {
- current = 0;
+ if (ticker == null || ticker.length() < 0) {
+ throw new IllegalArgumentException("Stock could not be found");
}
+ Result result = query(ticker, new DefaultRange(0, 1));
+ if (result.numRows != 1 || result.quotes.size() != 1) {
+ throw new IllegalArgumentException("Could not resolve stock ticker");
+ }
+ StockQuote quote = result.quotes.get(0);
+ // Perform the transaction with the user.
int quantity = transaction.getQuantity();
if (transaction.isBuy()) {
- current += quantity;
- // TODO: Verify player has enough funds.
- addFavorite(ticker);
+ ensurePlayer().buy(ticker, quantity, quote.getPrice());
} else {
- if (quantity > current) {
- throw new IllegalArgumentException(
- "You cannot sell more stock than you own");
- }
- current -= quantity;
+ ensurePlayer().sell(ticker, quantity, quote.getPrice());
}
- sharesOwnedBySymbol.put(ticker, current);
+
+ //
return new Transaction(true, ticker, quantity);
}
- private void generateFavoritesQuery() {
- StringBuilder sb = new StringBuilder(IMPOSSIBLE_TICKER_SYMBOL);
- for (String ticker : favorites) {
- sb.append('|');
- sb.append(ticker);
+ /**
+ * Ensure that a {@link PlayerStatus} for the current player exists and return
+ * it.
+ *
+ * @return the {@link PlayerStatus} for the current player
+ */
+ private PlayerStatus ensurePlayer() {
+ UserService userService = UserServiceFactory.getUserService();
+ User user = userService.getCurrentUser();
+ String userId = user.getUserId();
+ PlayerStatus player = players.get(userId);
+ if (player == null) {
+ player = new PlayerStatus();
+ players.put(userId, player);
}
- favoritesQuery = sb.toString();
+ return player;
}
private List<String> getTickers(String query) {
@@ -177,8 +189,16 @@
return false;
}
+ /**
+ * Query the remote service to retrieve current stock prices.
+ *
+ * @param query the query string
+ * @param range the range of results requested
+ * @return the stock quotes
+ */
private Result query(String query, Range range) {
// Get all symbols for the query.
+ PlayerStatus player = ensurePlayer();
List<String> symbols = getTickers(query);
if (symbols.size() == 0) {
@@ -258,8 +278,8 @@
try {
iprice = (int) (Double.parseDouble(price) * 100);
String name = companyNamesBySymbol.get(symbol);
- Integer sharesOwned = sharesOwnedBySymbol.get(symbol);
- boolean favorite = favorites.contains(symbol);
+ Integer sharesOwned = player.getSharesOwned(symbol);
+ boolean favorite = player.isFavorite(symbol);
priceMap.put(symbol, new StockQuote(symbol, name, iprice,
sharesOwned == null ? 0 : sharesOwned.intValue(), favorite));
} catch (NumberFormatException e) {
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockQuote.java b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockQuote.java
index 0689d51..851655b 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockQuote.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockQuote.java
@@ -38,17 +38,14 @@
* @param sharesOwned the number of shares owned by the player
* @param favorite true if the stock is in the player's favorites
*/
- public StockQuote(String ticker, String name, int price, int sharesOwned, boolean favorite) {
+ public StockQuote(String ticker, String name, int price, int sharesOwned,
+ boolean favorite) {
this.ticker = ticker;
this.name = name;
this.price = price;
this.sharesOwned = sharesOwned;
this.favorite = favorite;
}
-
- public int getSharesOwned() {
- return sharesOwned;
- }
/**
* Used for RPC.
@@ -59,7 +56,7 @@
public String getDisplayPrice() {
int dollars = getPrice() / 100;
int cents = getPrice() % 100;
-
+
StringBuilder sb = new StringBuilder();
sb.append("$ ");
sb.append(dollars);
@@ -82,7 +79,11 @@
public int getPrice() {
return price;
}
-
+
+ public int getSharesOwned() {
+ return sharesOwned;
+ }
+
public String getTicker() {
return ticker;
}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockRequest.java b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockRequest.java
index 76c16ac..e895339 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockRequest.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockRequest.java
@@ -19,15 +19,14 @@
import java.io.Serializable;
+/**
+ * A request for new stock data.
+ */
public class StockRequest implements Serializable {
-
+
String searchQuery;
Range searchRange;
Range favoritesRange;
-
- // Used by RPC
- public StockRequest() {
- }
public StockRequest(String searchQuery, Range searchRange,
Range favoritesRange) {
@@ -36,6 +35,16 @@
this.favoritesRange = favoritesRange;
}
+ /**
+ * Used by RPC.
+ */
+ StockRequest() {
+ }
+
+ public Range getFavoritesRange() {
+ return favoritesRange;
+ }
+
public String getSearchQuery() {
return searchQuery;
}
@@ -43,8 +52,4 @@
public Range getSearchRange() {
return searchRange;
}
-
- public Range getFavoritesRange() {
- return favoritesRange;
- }
}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockResponse.java b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockResponse.java
index f089cb0..fa560b0 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockResponse.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/StockResponse.java
@@ -28,32 +28,42 @@
private int numFavorites;
/**
+ * The amount of available cash in pennies.
+ */
+ private int cash;
+
+ /**
* Used for RPC.
*/
StockResponse() {
}
public StockResponse(StockQuoteList searchResults, StockQuoteList favorites,
- int numSearchResults, int numFavorites) {
+ int numSearchResults, int numFavorites, int cash) {
this.searchResults = searchResults;
this.favorites = favorites;
this.numSearchResults = numSearchResults;
this.numFavorites = numFavorites;
+ this.cash = cash;
}
- public StockQuoteList getSearchResults() {
- return searchResults;
+ public int getCash() {
+ return cash;
}
public StockQuoteList getFavorites() {
return favorites;
}
+ public int getNumFavorites() {
+ return numFavorites;
+ }
+
public int getNumSearchResults() {
return numSearchResults;
}
- public int getNumFavorites() {
- return numFavorites;
+ public StockQuoteList getSearchResults() {
+ return searchResults;
}
}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java
index 94b9cd8..01567a3 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java
@@ -40,16 +40,16 @@
Transaction() {
}
- public boolean isBuy() {
- return isBuy;
+ public int getQuantity() {
+ return quantity;
}
public String getTicker() {
return ticker;
}
- public int getQuantity() {
- return quantity;
+ public boolean isBuy() {
+ return isBuy;
}
@Override
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml
index 0855480..73c9336 100644
--- a/bikeshed/war/WEB-INF/web.xml
+++ b/bikeshed/war/WEB-INF/web.xml
@@ -4,21 +4,31 @@
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
-
+
<!-- Servlets -->
<servlet>
<servlet-name>stockServlet</servlet-name>
<servlet-class>com.google.gwt.sample.datawidgets.server.StockServiceImpl</servlet-class>
</servlet>
-
+
<servlet-mapping>
<servlet-name>stockServlet</servlet-name>
<url-pattern>/databackedwidgets/stock</url-pattern>
</servlet-mapping>
-
+
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>DataBackedWidgets.html</welcome-file>
</welcome-file-list>
+ <!-- Require login. -->
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>*</role-name>
+ </auth-constraint>
+ </security-constraint>
+
</web-app>