Adding list of top players to the game. git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7718 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/.classpath b/bikeshed/.classpath index 1031fe8..ce49847 100644 --- a/bikeshed/.classpath +++ b/bikeshed/.classpath
@@ -6,6 +6,7 @@ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> <classpathentry kind="var" path="GWT_TOOLS/redist/json/r2_20080312/json.jar" sourcepath="/GWT_TOOLS/redist/json/r2_20080312/json-src.jar"/> <classpathentry kind="lib" path="war/WEB-INF/lib/gwt-servlet.jar"/> - <classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER/Local"/> + <classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER/trunk"/> + <classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/> <classpathentry kind="output" path="war/WEB-INF/classes"/> </classpath>
diff --git a/bikeshed/.project b/bikeshed/.project index df86e23..a6c4379 100644 --- a/bikeshed/.project +++ b/bikeshed/.project
@@ -30,6 +30,16 @@ <arguments> </arguments> </buildCommand> + <buildCommand> + <name>com.google.appengine.eclipse.core.enhancerbuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>com.google.appengine.eclipse.core.projectValidator</name> + <arguments> + </arguments> + </buildCommand> </buildSpec> <natures> <nature>org.eclipse.jdt.core.javanature</nature> @@ -37,5 +47,6 @@ <nature>com.google.gdt.eclipse.core.webAppNature</nature> <nature>com.atlassw.tools.eclipse.checkstyle.CheckstyleNature</nature> <nature>org.eclipse.wst.common.project.facet.core.nature</nature> + <nature>com.google.appengine.eclipse.core.gaeNature</nature> </natures> </projectDescription>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.java new file mode 100644 index 0000000..a429f2f --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.java
@@ -0,0 +1,70 @@ +/* + * 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.bikeshed.sample.stocks.client; + +import com.google.gwt.bikeshed.cells.client.Cell; +import com.google.gwt.bikeshed.list.client.SimpleCellList; +import com.google.gwt.bikeshed.list.shared.ListModel; +import com.google.gwt.bikeshed.sample.stocks.shared.PlayerInfo; +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiFactory; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +/** + * Widget to display player scores. + */ +public class PlayerScoresWidget extends Composite { + + interface Binder extends UiBinder<Widget, PlayerScoresWidget> { + } + + private static final Binder binder = GWT.create(Binder.class); + + /** + * A {@link Cell} that displays the status of a single player. + */ + private static final class PlayerInfoCell extends Cell<PlayerInfo> { + @Override + public void render(PlayerInfo value, StringBuilder sb) { + sb.append("<div class='playerScoreBox'>"); + sb.append("<b>Name: </b>"); + sb.append(value.getDisplayName()); + sb.append("<br><b>Net Worth: </b>"); + sb.append(StockSample.getFormattedPrice(value.getNetWorth())); + sb.append("<br><b>Cash: </b>"); + sb.append(StockSample.getFormattedPrice(value.getCash())); + sb.append("</div>"); + } + } + + @UiField + SimpleCellList<PlayerInfo> listView; + + private final ListModel<PlayerInfo> model; + + public PlayerScoresWidget(ListModel<PlayerInfo> model) { + this.model = model; + initWidget(binder.createAndBindUi(this)); + } + + @UiFactory + SimpleCellList<PlayerInfo> createListView() { + return new SimpleCellList<PlayerInfo>(model, new PlayerInfoCell()); + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.ui.xml b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.ui.xml new file mode 100644 index 0000000..880366e --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/PlayerScoresWidget.ui.xml
@@ -0,0 +1,21 @@ +<ui:UiBinder + xmlns:ui='urn:ui:com.google.gwt.uibinder' + xmlns:g='urn:import:com.google.gwt.user.client.ui' + xmlns:l='urn:import:com.google.gwt.bikeshed.list.client' + xmlns:t='urn:import:com.google.gwt.bikeshed.tree.client' + xmlns:s='urn:import:com.google.gwt.bikeshed.sample.stocks.client'> + + <ui:style field='common' src='common.css'/> + + <g:DockLayoutPanel unit='EM'> + <g:north size='2'> + <g:Label styleName='{common.header}'>Everyone Else</g:Label> + </g:north> + + <g:center> + <g:ScrollPanel styleName="{common.padded}"> + <l:SimpleCellList ui:field='listView'/> + </g:ScrollPanel> + </g:center> + </g:DockLayoutPanel> +</ui:UiBinder>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java index 93d87df..a7d8e19 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java
@@ -1,12 +1,12 @@ /* * 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 @@ -21,6 +21,7 @@ import com.google.gwt.bikeshed.list.shared.Range; import com.google.gwt.bikeshed.list.shared.AsyncListModel.DataSource; import com.google.gwt.bikeshed.sample.stocks.client.TransactionTreeViewModel.SectorListModel; +import com.google.gwt.bikeshed.sample.stocks.shared.PlayerInfo; import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote; import com.google.gwt.bikeshed.sample.stocks.shared.StockQuoteList; import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest; @@ -69,6 +70,7 @@ @UiField FavoritesWidget favoritesWidget; @UiField Label netWorthLabel; + @UiField PlayerScoresWidget playerScoresWidget; @UiField StockQueryWidget queryWidget; @UiField SideBySideTreeView transactionTree; @@ -79,6 +81,7 @@ private final StockServiceAsync dataService = GWT.create(StockService.class); private AsyncListModel<StockQuote> favoritesListModel; + private AsyncListModel<PlayerInfo> playerScoresListModel; private AsyncListModel<StockQuote> searchListModel; private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker = new HashMap<String, ListListModel<Transaction>>(); @@ -122,8 +125,15 @@ } }); - treeModel = new TransactionTreeViewModel(this, - favoritesListModel, transactionListListModelsByTicker); + playerScoresListModel = new AsyncListModel<PlayerInfo>( + new DataSource<PlayerInfo>() { + public void requestData(AsyncListModel<PlayerInfo> listModel) { + // Player cannot request data from this view. + } + }); + + treeModel = new TransactionTreeViewModel(this, favoritesListModel, + transactionListListModelsByTicker); transactionListModel = new ListListModel<Transaction>(); transactions = transactionListModel.getList(); @@ -164,7 +174,7 @@ /** * Process the {@link StockResponse} from the server. - * + * * @param response the stock response */ public void processStockResponse(StockResponse response) { @@ -178,6 +188,12 @@ updateFavorites(response); updateSector(response); + // Update the player scores. + List<PlayerInfo> playerScores = response.getPlayers(); + int numPlayers = playerScores.size(); + playerScoresListModel.updateDataSize(numPlayers, true); + playerScoresListModel.updateViewData(0, numPlayers, playerScores); + // Update available cash. int cash = response.getCash(); int netWorth = response.getNetWorth(); @@ -196,7 +212,7 @@ /** * Set or unset a ticker symbol as a 'favorite'. - * + * * @param ticker the ticker symbol * @param favorite if true, make the stock a favorite */ @@ -204,28 +220,28 @@ if (favorite) { dataService.addFavorite(ticker, favoritesListModel.getRanges()[0], new AsyncCallback<StockResponse>() { - public void onFailure(Throwable caught) { - Window.alert("Error adding favorite"); - } + public void onFailure(Throwable caught) { + handleRpcError(caught, "Error adding favorite"); + } - public void onSuccess(StockResponse response) { - updateFavorites(response); - } - }); + public void onSuccess(StockResponse response) { + updateFavorites(response); + } + }); } else { dataService.removeFavorite(ticker, favoritesListModel.getRanges()[0], new AsyncCallback<StockResponse>() { - public void onFailure(Throwable caught) { - Window.alert("Error removing favorite"); - } + public void onFailure(Throwable caught) { + handleRpcError(caught, "Error removing favorite"); + } - public void onSuccess(StockResponse response) { - updateFavorites(response); - } - }); + public void onSuccess(StockResponse response) { + updateFavorites(response); + } + }); } } - + public void transact(Transaction t) { dataService.transact(t, new AsyncCallback<Transaction>() { public void onFailure(Throwable caught) { @@ -238,19 +254,17 @@ } /** - * Update transactions (list of all transactions), - * transactionTickers (set of all tickers involved in - * transactions), and transactionsByTicker (map from - * ticker to lists of transactions for that ticker). + * Update transactions (list of all transactions), transactionTickers (set + * of all tickers involved in transactions), and transactionsByTicker (map + * from ticker to lists of transactions for that ticker). */ private void recordTransaction(Transaction result) { transactions.add(0, result); String ticker = result.getTicker(); - + // Update the next level of the transaction tree // for the given ticker - ListListModel<Transaction> t = - transactionListListModelsByTicker.get(ticker); + ListListModel<Transaction> t = transactionListListModelsByTicker.get(ticker); if (t == null) { t = new ListListModel<Transaction>(); transactionListListModelsByTicker.put(ticker, t); @@ -272,10 +286,12 @@ Range[] searchRanges = searchListModel.getRanges(); Range[] favoritesRanges = favoritesListModel.getRanges(); - + String sectorName = getSectorName(); - SectorListModel sectorListModel = sectorName != null ? treeModel.getSectorListModel(sectorName) : null; - Range[] sectorRanges = sectorListModel == null ? null : sectorListModel.getRanges(); + SectorListModel sectorListModel = sectorName != null + ? treeModel.getSectorListModel(sectorName) : null; + Range[] sectorRanges = sectorListModel == null ? null + : sectorListModel.getRanges(); if (searchRanges == null || searchRanges.length == 0 || favoritesRanges == null || favoritesRanges.length == 0) { @@ -283,20 +299,14 @@ } String searchQuery = queryWidget.getSearchQuery(); - + StockRequest request = new StockRequest(searchQuery, sectorListModel != null ? sectorListModel.getSector() : null, - searchRanges[0], - favoritesRanges[0], - sectorRanges != null && sectorRanges.length > 0 ? sectorRanges[0] : null); + searchRanges[0], favoritesRanges[0], sectorRanges != null + && sectorRanges.length > 0 ? sectorRanges[0] : null); dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() { public void onFailure(Throwable caught) { - String message = caught.getMessage(); - if (message.contains("Not logged in")) { - // Force the user to login. - Window.Location.reload(); - } else { - Window.alert("ERROR: " + caught.getMessage()); + if (handleRpcError(caught, null)) { updateTimer.schedule(UPDATE_DELAY); } } @@ -334,6 +344,11 @@ } @UiFactory + PlayerScoresWidget createPlayerScoresWidget() { + return new PlayerScoresWidget(playerScoresListModel); + } + + @UiFactory StockQueryWidget createQueryWidget() { return new StockQueryWidget(searchListModel, this); } @@ -352,7 +367,30 @@ return (String) childNode.getValue(); } } - + return null; } + + /** + * Display a message to the user when an RPC call fails. + * + * @param caught the exception + * @param displayMessage the message to display to the user, or null to + * display a default message + * @return true if recoverable, false if not + */ + private boolean handleRpcError(Throwable caught, String displayMessage) { + String message = caught.getMessage(); + if (message.contains("Not logged in")) { + // Force the user to login. + Window.Location.reload(); + return false; + } + + if (displayMessage == null) { + displayMessage = "ERROR: " + caught.getMessage(); + } + Window.alert(displayMessage); + return true; + } }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.ui.xml b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.ui.xml index a8da167..e34762c 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.ui.xml +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.ui.xml
@@ -25,16 +25,7 @@ </g:north> <g:center> - <g:ScrollPanel styleName='{common.bg}'> - <g:HTMLPanel> - <div class='{common.header}'>Everyone Else</div> - <table> - <tr><td>Dan Rice</td><td>$10000</td></tr> - <tr><td>Joel Webber</td><td>$10000</td></tr> - <tr><td>John Labanca</td><td>$10000</td></tr> - </table> - </g:HTMLPanel> - </g:ScrollPanel> + <s:PlayerScoresWidget styleName='{common.bg}' ui:field='playerScoresWidget'/> </g:center> </g:DockLayoutPanel> </g:west>
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/common.css b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/common.css index 6ff6db3..717e19e 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/common.css +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/common.css
@@ -45,3 +45,7 @@ .table tfoot th { border-width: 0px; } + +.padded { + padding: 5px; +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java index ea6a4e9..781ef08 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java
@@ -15,6 +15,8 @@ */ package com.google.gwt.bikeshed.sample.stocks.server; +import com.google.gwt.bikeshed.sample.stocks.shared.PlayerInfo; + import java.util.HashMap; import java.util.TreeSet; import java.util.regex.Pattern; @@ -22,12 +24,7 @@ /** * 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; +public class PlayerStatus extends PlayerInfo { /** * An impossible stock ticker. @@ -35,15 +32,10 @@ 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>(); - + /** * A precompiled version of the favorites query. */ @@ -58,13 +50,14 @@ * The number of shares owned for each symbol. */ private HashMap<String, Integer> sharesOwnedBySymbol = new HashMap<String, Integer>(); - + /** * The total amount paid for each symbol. */ private HashMap<String, Double> averagePriceBySymbol = new HashMap<String, Double>(); - public PlayerStatus() { + public PlayerStatus(String name) { + super(name); generateFavoritesQuery(); } @@ -89,6 +82,7 @@ public void buy(String ticker, int quantity, int price) throws IllegalArgumentException { // Verify that the player can afford the stock. + int cash = getCash(); int totalPrice = price * quantity; if (cash < totalPrice) { throw new IllegalArgumentException("You cannot afford that much stock"); @@ -98,16 +92,16 @@ int current = getSharesOwned(ticker); double averagePrice = getAveragePrice(ticker); double totalPaid = averagePrice * current + totalPrice; - + // Update case and shares owned current += quantity; - cash -= totalPrice; + setCash(cash - totalPrice); sharesOwnedBySymbol.put(ticker, current); - + // Update average price averagePrice = totalPaid / current; averagePriceBySymbol.put(ticker, averagePrice); - + // Add this stock to the favorites list. addFavorite(ticker); } @@ -115,7 +109,7 @@ /** * Returns the total cost of the currently owned shared, using an average cost * basis method. - * + * * @param ticker the stock ticker */ public int getAverageCostBasis(String ticker) { @@ -128,15 +122,6 @@ } /** - * Get the player's current cash amount. - * - * @return the cash amount - */ - public int getCash() { - return cash; - } - - /** * Get this players favorite pattern. * * @return the pattern @@ -164,7 +149,7 @@ Integer current = sharesOwnedBySymbol.get(ticker); return current == null ? 0 : current; } - + /** * Check if the stock ticker is in the favorites list. * @@ -204,10 +189,11 @@ // Perform the transaction. int totalPrice = price * quantity; - cash += totalPrice; + setCash(getCash() + totalPrice); + current -= quantity; sharesOwnedBySymbol.put(ticker, current); - + if (current == 0) { sharesOwnedBySymbol.remove(ticker); averagePriceBySymbol.remove(ticker); @@ -215,6 +201,11 @@ } } + @Override + public void setStockValue(int value) { + super.setStockValue(value); + } + /** * Regenerate the favorites query. */
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java index 95b47f6..17e09c2 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java
@@ -15,9 +15,13 @@ */ package com.google.gwt.bikeshed.sample.stocks.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.bikeshed.list.shared.Range; import com.google.gwt.bikeshed.list.shared.AbstractListModel.DefaultRange; import com.google.gwt.bikeshed.sample.stocks.client.StockService; +import com.google.gwt.bikeshed.sample.stocks.shared.PlayerInfo; import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote; import com.google.gwt.bikeshed.sample.stocks.shared.StockQuoteList; import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest; @@ -25,7 +29,11 @@ import com.google.gwt.bikeshed.sample.stocks.shared.Transaction; import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -46,7 +54,7 @@ String change; long createdTime; int price; - + public Quote(int price, String change) { this.price = price; this.change = change; @@ -65,7 +73,7 @@ return price; } } - + /** * The result of a query to the remote service that provides stock quotes. */ @@ -127,7 +135,7 @@ "ZMH"); // Precompile each regex - for (Map.Entry<String,String> entry : sectorQueries.entrySet()) { + for (Map.Entry<String, String> entry : sectorQueries.entrySet()) { sectorPatterns.put(entry.getKey(), compile(entry.getValue())); } } @@ -150,8 +158,7 @@ PlayerStatus player = ensurePlayer(); player.addFavorite(ticker); Result favorites = queryFavorites(favoritesRange); - return new StockResponse(null, favorites.quotes, null, null, - 0, favorites.numRows, 0, player.getCash()); + return createStockResponse(player, null, favorites, null, null); } public Result getSectorQuotes(String sector, Range sectorRange) { @@ -182,22 +189,15 @@ Result sector = sectorRange != null ? getSectorQuotes(sectorName, sectorRange) : null; - return new StockResponse(searchResults.quotes, - favorites.quotes, - sector != null ? sectorName : null, - sector != null ? sector.quotes : null, - searchResults.numRows, - favorites.numRows, - sector != null ? sector.numRows : 0, - player.getCash()); + return createStockResponse(player, searchResults, favorites, sectorName, + sector); } - + public StockResponse removeFavorite(String ticker, Range favoritesRange) { PlayerStatus player = ensurePlayer(); player.removeFavorite(ticker); Result favorites = queryFavorites(favoritesRange); - return new StockResponse(null, favorites.quotes, null, null, - 0, favorites.numRows, 0, player.getCash()); + return createStockResponse(player, null, favorites, null, null); } public Transaction transact(Transaction transaction) @@ -227,16 +227,63 @@ } /** + * Create a stock response, updating the current user's net worth. + * + * @param player the player info + * @param searchResults the search results + * @param favorites the users favorites + * @param sectorName the name of the sector + * @param sectorResults the sector results + * @return a {@link StockResponse} + */ + private StockResponse createStockResponse(PlayerStatus player, + Result searchResults, Result favorites, String sectorName, + Result sectorResults) { + // Default to no search results. + if (searchResults == null) { + searchResults = new Result(null, 0); + } + + // Default to no sector results. + if (sectorResults == null) { + sectorResults = new Result(null, 0); + } + + // Store the new stock value. + player.setStockValue(favorites.quotes.getValue()); + + // Create a stock response. + List<PlayerInfo> playerScores = new ArrayList<PlayerInfo>(); + for (PlayerStatus curPlayer : players.values()) { + playerScores.add(curPlayer.copy()); + } + Collections.sort(playerScores, new Comparator<PlayerInfo>() { + public int compare(PlayerInfo o1, PlayerInfo o2) { + // Reverse sort so top player is first. + return o2.getNetWorth() - o1.getNetWorth(); + } + }); + StockResponse response = new StockResponse(player.copy(), + searchResults.quotes, favorites.quotes, sectorName != null ? sectorName + : null, sectorResults.quotes, searchResults.numRows, + favorites.numRows, sectorResults.numRows, playerScores); + + return response; + } + + /** * Ensure that a {@link PlayerStatus} for the current player exists and return * it. - * + * * @return the {@link PlayerStatus} for the current player */ private PlayerStatus ensurePlayer() { - String userId = "I Am the User"; + UserService userService = UserServiceFactory.getUserService(); + User user = userService.getCurrentUser(); + String userId = user.getUserId(); PlayerStatus player = players.get(userId); if (player == null) { - player = new PlayerStatus(); + player = new PlayerStatus(user.getNickname()); players.put(userId, player); } return player; @@ -245,11 +292,11 @@ private Result getQuotes(SortedSet<String> symbols, Range range) { int start = range.getStart(); int end = Math.min(start + range.getLength(), symbols.size()); - + if (end <= start) { return new Result(new StockQuoteList(0), 0); } - + // Get the symbols that are in range. SortedSet<String> symbolsInRange = new TreeSet<String>(); int idx = 0; @@ -259,10 +306,10 @@ } idx++; } - + // If we already have a price that is less than 5 seconds old, // don't re-request the data from the server - + SortedSet<String> symbolsToQuery = new TreeSet<String>(); long now = System.currentTimeMillis(); for (String symbol : symbolsInRange) { @@ -271,19 +318,20 @@ symbolsToQuery.add(symbol); // System.out.println("retrieving new value of " + symbol); } else { - // System.out.println("Using cached value of " + symbol + " (" + (now - quote.getCreatedTime()) + "ms old)"); + // System.out.println("Using cached value of " + symbol + " (" + (now - + // quote.getCreatedTime()) + "ms old)"); } } - + if (symbolsToQuery.size() > 0) { GoogleFinance.queryServer(symbolsToQuery, QUOTES); } - + // Create and return a StockQuoteList containing the quotes StockQuoteList toRet = new StockQuoteList(start); for (String symbol : symbolsInRange) { Quote quote = QUOTES.get(symbol); - + if (quote == null) { System.out.println("Bad symbol " + symbol); } else { @@ -292,44 +340,45 @@ Integer sharesOwned = player.getSharesOwned(symbol); boolean favorite = player.isFavorite(symbol); int totalPaid = player.getAverageCostBasis(symbol); - + toRet.add(new StockQuote(symbol, name, quote.getPrice(), - quote.getChange(), sharesOwned == null ? 0 : sharesOwned.intValue(), - favorite, totalPaid)); + quote.getChange(), + sharesOwned == null ? 0 : sharesOwned.intValue(), favorite, + totalPaid)); } } - + return new Result(toRet, toRet.size()); } // If a query is alpha-only ([A-Za-z]+), return stocks for which: - // 1a) a prefix of the ticker symbol matches the query - // 2) any substring of the stock name matches the query + // 1a) a prefix of the ticker symbol matches the query + // 2) any substring of the stock name matches the query // // If a query is non-alpha, consider it as a regex and return stocks for // which: - // 1b) any portion of the stock symbol matches the regex - // 2) any portion of the stock name matches the regex + // 1b) any portion of the stock symbol matches the regex + // 2) any portion of the stock name matches the regex private Result getSearchQuotes(String query, Range searchRange) { SortedSet<String> symbols = new TreeSet<String>(); - + boolean queryIsAlpha = true; for (int i = 0; i < query.length(); i++) { char c = query.charAt(i); if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) { queryIsAlpha = false; break; - } + } } // Canonicalize case query = query.toUpperCase(Locale.US); - + // (1a) if (queryIsAlpha) { getTickersByPrefix(query, symbols); } - + // Use Unicode case-insensitive matching, allow matching of a substring Pattern pattern = compile("(?iu).*(" + query + ").*"); if (pattern != null) { @@ -337,7 +386,7 @@ if (!queryIsAlpha) { getTickersBySymbolRegex(pattern, symbols); } - + // (2) if (query.length() > 2) { getTickersByNameRegex(pattern, symbols); @@ -352,29 +401,29 @@ if (pattern == null) { return; } - - for (Map.Entry<String,String> entry : Stocks.companyNamesBySymbol.entrySet()) { + + for (Map.Entry<String, String> entry : Stocks.companyNamesBySymbol.entrySet()) { if (tickers.size() >= MAX_RESULTS_TO_RETURN) { return; } - + if (match(entry.getValue(), pattern)) { tickers.add(entry.getKey()); } } } - + // Assume prefix is upper case private void getTickersByPrefix(String prefix, Set<String> tickers) { if (prefix == null || prefix.length() == 0) { return; } - + for (String ticker : Stocks.stockTickers) { if (tickers.size() >= MAX_RESULTS_TO_RETURN) { break; } - + if (ticker.startsWith(prefix)) { tickers.add(ticker); } @@ -386,7 +435,7 @@ if (pattern == null) { return; } - + for (String ticker : Stocks.stockTickers) { if (tickers.size() >= MAX_RESULTS_TO_RETURN) { return; @@ -396,7 +445,7 @@ } } } - + private boolean match(String symbol, Pattern pattern) { Matcher m = pattern.matcher(symbol); return m.matches(); @@ -416,7 +465,7 @@ if (favoritesPattern != null) { getTickersBySymbolRegex(favoritesPattern, symbols); } - + return getQuotes(symbols, favoritesRange); }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/PlayerInfo.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/PlayerInfo.java new file mode 100644 index 0000000..302e898 --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/PlayerInfo.java
@@ -0,0 +1,129 @@ +/* + * 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.bikeshed.sample.stocks.shared; + +import java.io.Serializable; + +/** + * Information about a single player. + */ +public class PlayerInfo implements Serializable { + + /** + * The initial amount of cash that the player starts with, in cents. + */ + private static final int INITIAL_CASH = 10000 * 100; + + /** + * The net worth of the player in cents. + */ + private int cash = INITIAL_CASH; + + /** + * The players name. + */ + private String name; + + /** + * The net worth of the player in cents. + */ + private int stockValue; + + public PlayerInfo(String name) { + this.name = name; + } + + /** + * Visible for RPC. + */ + PlayerInfo() { + } + + public PlayerInfo copy() { + PlayerInfo copy = new PlayerInfo(name); + copy.setCash(cash); + copy.setStockValue(stockValue); + return copy; + } + + /** + * Get the player's current cash amount. + * + * @return the cash amount + */ + public int getCash() { + return cash; + } + + /** + * Get the player's display name, which excludes domain information. + * + * @return the display name of the player. + */ + public String getDisplayName() { + String displayName = name; + int domain = displayName.indexOf('@'); + if (domain > 0) { + displayName = displayName.substring(0, domain); + } + return displayName; + } + + /** + * Get the player's name. + * + * @return the name of the player. + */ + public String getName() { + return name; + } + + /** + * Get the players net worth, including cash and stock value. + * + * @return the net worth in cents + */ + public int getNetWorth() { + return cash + stockValue; + } + + /** + * Get the value of this player's stock. + * + * @return the value of the stock in cents + */ + public int getStockValue() { + return stockValue; + } + + /** + * Set the amount of cash that the user has. + * + * @param cash the user's cash in cents + */ + protected void setCash(int cash) { + this.cash = cash; + } + + /** + * Set the value of this player's stock. + * + * @param value the stock value in cents + */ + protected void setStockValue(int value) { + this.stockValue = value; + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java index e8844c3..0a6bed1 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java
@@ -16,6 +16,7 @@ package com.google.gwt.bikeshed.sample.stocks.shared; import java.io.Serializable; +import java.util.List; /** * A response to a request for stock data. @@ -25,7 +26,6 @@ /** * The amount of available cash in pennies. */ - private int cash; private StockQuoteList favorites; private int numFavorites; private int numSearchResults; @@ -33,9 +33,14 @@ private StockQuoteList searchResults; private StockQuoteList sector; private String sectorName; + private PlayerInfo player; + private List<PlayerInfo> players; - public StockResponse(StockQuoteList searchResults, StockQuoteList favorites, - String sectorName, StockQuoteList sector, int numSearchResults, int numFavorites, int numSector, int cash) { + public StockResponse(PlayerInfo player, StockQuoteList searchResults, + StockQuoteList favorites, String sectorName, StockQuoteList sector, + int numSearchResults, int numFavorites, int numSector, + List<PlayerInfo> players) { + this.player = player; this.searchResults = searchResults; this.favorites = favorites; this.sectorName = sectorName; @@ -43,7 +48,7 @@ this.numSearchResults = numSearchResults; this.numFavorites = numFavorites; this.numSector = numSector; - this.cash = cash; + this.players = players; } /** @@ -53,18 +58,18 @@ } public int getCash() { - return cash; + return player.getCash(); } public StockQuoteList getFavorites() { return favorites; } - + /** - * The sum of cash available and portfolio value. + * The sum of cash available and portfolio value. */ public int getNetWorth() { - return cash + favorites.getValue(); + return player.getNetWorth(); } public int getNumFavorites() { @@ -79,6 +84,15 @@ return numSector; } + /** + * Get the list of all players and their scores. + * + * @return the ordered list of all players + */ + public List<PlayerInfo> getPlayers() { + return players; + } + public StockQuoteList getSearchResults() { return searchResults; } @@ -86,7 +100,7 @@ public StockQuoteList getSector() { return sector; } - + public String getSectorName() { return sectorName; }
diff --git a/bikeshed/war/Stocks.css b/bikeshed/war/Stocks.css index 3bc0cb0..c26862c 100644 --- a/bikeshed/war/Stocks.css +++ b/bikeshed/war/Stocks.css
@@ -60,6 +60,13 @@ color: red; } +.playerScoreBox { + border: 1px solid #777; + padding:3px; + background: #eee; + margin-bottom: 5px; +} + /** Set ids using widget.getElement().setId("idOfElement") */ #closeButton { margin: 15px 6px 6px;
diff --git a/bikeshed/war/WEB-INF/web.xml b/bikeshed/war/WEB-INF/web.xml index 4f123b4..38aa974 100644 --- a/bikeshed/war/WEB-INF/web.xml +++ b/bikeshed/war/WEB-INF/web.xml
@@ -41,4 +41,14 @@ <welcome-file>Tree.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>