net.officefloor.demo.stocks.client.StockWatchWidget.java Source code

Java tutorial

Introduction

Here is the source code for net.officefloor.demo.stocks.client.StockWatchWidget.java

Source

/*
 * OfficeFloor - http://www.officefloor.net
 * Copyright (C) 2005-2013 Daniel Sagenschneider
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.officefloor.demo.stocks.client;

import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import net.officefloor.plugin.gwt.comet.api.OfficeFloorComet;

import com.google.gwt.cell.client.SafeHtmlCell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.cellview.client.CellTable.Resources;
import com.google.gwt.user.cellview.client.CellTable.Style;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;

/**
 * <p>
 * {@link Widget} for watching {@link StockPrice}.
 * <p>
 * This provides the hooks for listening on {@link StockPrice} instances.
 * 
 * @author Daniel Sagenschneider
 */
public class StockWatchWidget extends VerticalPanel {

    /**
     * Displayed {@link StockPrice} instances.
     */
    private final ListDataProvider<StockEntry> displayedPrices = new ListDataProvider<StockEntry>();

    /**
     * {@link StockInterest}.
     */
    private final StockInterest filter = new StockInterest();

    /**
     * Template to render the {@link CellTable} headers.
     */
    static interface HeaderTemplate extends SafeHtmlTemplates {
        @SafeHtmlTemplates.Template("<span class=\"StockWatchHeader\">{0}</span>")
        SafeHtml header(String text);
    }

    private final HeaderTemplate headerTemplate = GWT.create(HeaderTemplate.class);

    /**
     * Template to render the {@link Stock} name.
     */
    static interface NameTemplate extends SafeHtmlTemplates {
        @SafeHtmlTemplates.Template("<span class=\"StockWatchName\">{0}</span>")
        SafeHtml cell(String text);
    }

    private final NameTemplate nameTemplate = GWT.create(NameTemplate.class);

    /**
     * Template to render the {@link StockPrice} change.
     */
    static interface ChangeTemplate extends SafeHtmlTemplates {
        @SafeHtmlTemplates.Template("<span class=\"{0} {2}\">{1}</span>")
        SafeHtml cell(String cssClass, String text, String highlightCssClass);
    }

    private final ChangeTemplate changeTemplate = GWT.create(ChangeTemplate.class);

    /**
     * Template to render the {@link StockPrice} size/price.
     */
    static interface NumberTemplate extends SafeHtmlTemplates {
        @SafeHtmlTemplates.Template("<span class=\"StockWatchNumber {1}\">{0}</span>")
        SafeHtml cell(String text, String highlightCssClass);
    }

    private final NumberTemplate numberTemplate = GWT.create(NumberTemplate.class);

    /**
     * Extracts the {@link SafeHtml} for the {@link Column}.
     */
    private interface ColumnValue {

        /**
         * Obtains the {@link SafeHtml} value.
         * 
         * @param entry
         *            {@link StockEntry}.
         * @return {@link SafeHtml}.
         */
        SafeHtml getValue(StockEntry entry);
    }

    /**
     * Initiate.
     * 
     * @param pageSize
     *            Number of {@link Stock} instances displayed per page.
     */
    public StockWatchWidget(int pageSize) {

        // Locale specific formatters
        final DateTimeFormat timeFormat = DateTimeFormat.getFormat(PredefinedFormat.HOUR24_MINUTE_SECOND);
        final NumberFormat sizeFormat = NumberFormat.getDecimalFormat();
        final NumberFormat priceFormat = NumberFormat.getFormat("0.00");
        final NumberFormat percentFormat = NumberFormat.getFormat("0.00");

        // Configure table
        final CellTable<StockEntry> table = new CellTable<StockEntry>(pageSize, new StockWatchResource());
        this.addColumn(table, "Name", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = entry.getStock().getName();
                return StockWatchWidget.this.nameTemplate.cell(text);
            }
        });
        this.addColumn(table, "Open", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = priceFormat.format(entry.getOpenPrice());
                return StockWatchWidget.this.numberTemplate.cell(text, "");
            }
        });
        this.addColumn(table, "Time", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = timeFormat.format(new Date(entry.getStockPrice().getTimestamp()));
                return StockWatchWidget.this.numberTemplate.cell(text, entry.getHighlightCssClass());
            }
        });
        this.addColumn(table, "Change", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                double change = entry.getOpenPercentageChange() * 100;
                String text = percentFormat.format(change) + "%";
                String cssClass;
                if (change < 0) {
                    cssClass = "StockWatchChangeNegative";
                } else {
                    text = "+" + text;
                    cssClass = "StockWatchChangePositive";
                }
                return StockWatchWidget.this.changeTemplate.cell(cssClass, text, entry.getHighlightCssClass());
            }
        });
        this.addColumn(table, "Size", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = sizeFormat.format(Integer.valueOf(entry.getStockPrice().getBidSize()));
                return StockWatchWidget.this.numberTemplate.cell(text, entry.getHighlightCssClass());
            }
        });
        this.addColumn(table, "Bid", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = priceFormat.format(entry.getStockPrice().getBidPrice());
                return StockWatchWidget.this.numberTemplate.cell(text, entry.getHighlightCssClass());
            }
        });
        this.addColumn(table, "Ask", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = priceFormat.format(entry.getStockPrice().getAskPrice());
                return StockWatchWidget.this.numberTemplate.cell(text, entry.getHighlightCssClass());
            }
        });
        this.addColumn(table, "Size", new ColumnValue() {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                String text = sizeFormat.format(Integer.valueOf(entry.getStockPrice().getAskSize()));
                return StockWatchWidget.this.numberTemplate.cell(text, entry.getHighlightCssClass());
            }
        });

        // Add list data provider to manage the data
        this.displayedPrices.addDataDisplay(table);

        // Subscribe to stock price events
        OfficeFloorComet.subscribe(StockPriceEvents.class, new StockPriceEvents() {
            @Override
            public void event(StockPrice price, Stock stock) {

                // Remove/Add stock price to add/update it
                List<StockEntry> stockEntries = StockWatchWidget.this.displayedPrices.getList();

                // Find the stock entry
                StockEntry stockEntry = null;
                for (StockEntry entry : stockEntries) {
                    if (entry.getStock().getMarketId().equals(stock.getMarketId())) {
                        stockEntry = entry;
                        break; // found entry
                    }
                }
                if (stockEntry == null) {
                    return; // stock not being watched
                }

                // Update the price
                stockEntry.setCurrentPrice(price);

                // Refresh the rows for the new price
                StockWatchWidget.this.displayedPrices.refresh();

                // Sort the rows
                StockWatchWidget.this.sortRows();
            }
        }, this.filter);

        // Add the table
        this.add(table);
    }

    /**
     * Flags to watch the particular {@link Stock}.
     * 
     * @param stock
     *            {@link Stock}.
     * @param openPrice
     *            Open {@link StockPrice} for the {@link Stock}.
     */
    public void watchStock(Stock stock, StockPrice openPrice) {

        // Add stock to display
        this.displayedPrices.getList().add(new StockEntry(stock, openPrice));
        this.sortRows();

        // Take interest in the Stock
        this.filter.addMarketId(stock.getMarketId());
    }

    /**
     * Flags to stop watching the particular {@link Stock}.
     * 
     * @param stock
     *            {@link Stock} to stop watching.
     */
    public void unwatchStock(Stock stock) {

        // Stop interest in the Stock
        this.filter.removeMarketId(stock.getMarketId());

        // Remove stock from display
        for (Iterator<StockEntry> iterator = this.displayedPrices.getList().iterator(); iterator.hasNext();) {
            StockEntry entry = iterator.next();
            if (entry.getStock().equals(stock)) {
                iterator.remove();
            }
        }
    }

    /**
     * Sorts the rows.
     */
    private void sortRows() {
        Collections.sort(this.displayedPrices.getList(), new Comparator<StockEntry>() {
            @Override
            public int compare(StockEntry a, StockEntry b) {
                return String.CASE_INSENSITIVE_ORDER.compare(a.getStock().getName(), b.getStock().getName());
            }
        });
    }

    /**
     * Adds {@link Column} to the {@link CellTable}.
     * 
     * @param table
     *            {@link CellTable}.
     * @param columnName
     *            Name of the {@link Column}.
     * @param columnValue
     *            {@link ColumnValue} to obtain value for the {@link Column}.
     * @return Added {@link Column}.
     */
    private Column<StockEntry, SafeHtml> addColumn(CellTable<StockEntry> table, String columnName,
            final ColumnValue columnValue) {

        // Create the column
        Column<StockEntry, SafeHtml> column = new Column<StockEntry, SafeHtml>(new SafeHtmlCell()) {
            @Override
            public SafeHtml getValue(StockEntry entry) {
                return columnValue.getValue(entry);
            }
        };

        // Add header for the column
        SafeHtml header = this.headerTemplate.header(columnName);

        // Add and return the column
        table.addColumn(column, header);
        return column;
    }

    /**
     * Interest in the {@link Stock} used to subscribe to only the specified
     * {@link Stock} instances.
     */
    public static class StockInterest implements IsSerializable {

        /**
         * Market identifiers that have interest in.
         */
        private Set<String> marketIdentifiers = new HashSet<String>();

        /**
         * Adds a market identifier.
         * 
         * @param marketId
         *            Market identifier.
         */
        public void addMarketId(String marketId) {
            this.marketIdentifiers.add(marketId);
        }

        /**
         * Removes the market identifier.
         * 
         * @param marketId
         *            Market identifier.
         */
        public void removeMarketId(String marketId) {
            this.marketIdentifiers.remove(marketId);
        }

        /*
         * ====================== Object ======================
         */

        @Override
        public boolean equals(Object obj) {

            // Should be matched against a Stock for interest filtering
            if (!(obj instanceof Stock)) {
                return false;
            }
            Stock stock = (Stock) obj;

            // Watch all stocks if none specified
            if (this.marketIdentifiers.size() == 0) {
                return true;
            }

            // Equal if stock is of interest
            return this.marketIdentifiers.contains(stock.getMarketId());
        }
    }

    /**
     * {@link Stock} entry within the {@link CellTable}.
     */
    private class StockEntry {

        /**
         * {@link Stock}.
         */
        private final Stock stock;

        /**
         * Open {@link StockPrice}.
         */
        private final StockPrice openPrice;

        /**
         * Last {@link StockPrice}.
         */
        private StockPrice lastPrice;

        /**
         * Current {@link StockPrice}.
         */
        private StockPrice currentPrice;

        /**
         * Highlight CSS class.
         */
        private String highlightCssClass = "";

        /**
         * Initiate.
         * 
         * @param stock
         *            {@link Stock}.
         * @param openPrice
         *            Open {@link StockPrice}.
         */
        public StockEntry(Stock stock, StockPrice openPrice) {
            this.stock = stock;
            this.openPrice = openPrice;
            this.lastPrice = openPrice;
            this.currentPrice = openPrice;
        }

        /**
         * Updates the current {@link StockPrice}.
         * 
         * @param price
         *            Current {@link StockPrice}.
         */
        public void setCurrentPrice(StockPrice price) {
            this.lastPrice = this.currentPrice;
            this.currentPrice = price;

            // Provide highlight for change
            double lastPriceChange = this.getLastPriceChange();
            this.highlightCssClass = (lastPriceChange >= 0 ? "StockWatchHighlightPositive"
                    : "StockWatchHighlightNegative");

            // Clear highlight after a moment
            new Timer() {
                @Override
                public void run() {
                    // Reset highlight
                    StockEntry.this.highlightCssClass = "";

                    // Refresh display for no highlight
                    StockWatchWidget.this.displayedPrices.refresh();
                }
            }.schedule(1000);
        }

        /**
         * Obtains the highlight CSS class.
         * 
         * @return Highlight CSS class.
         */
        public String getHighlightCssClass() {
            return this.highlightCssClass;
        }

        /**
         * Obtains the {@link Stock}.
         * 
         * @return {@link Stock}.
         */
        public Stock getStock() {
            return this.stock;
        }

        /**
         * Obtains the open ask price.
         * 
         * @return Open ask price.
         */
        public double getOpenPrice() {
            return this.openPrice.getAskPrice();
        }

        /**
         * Obtains the percentage change in ask price since market open.
         * 
         * @return Percentage change in ask price since market open.
         */
        public double getOpenPercentageChange() {
            StockPrice price = this.getStockPrice();
            double openAskPrice = this.openPrice.getAskPrice();
            double change = price.getAskPrice() - openAskPrice;
            double percentageChange = change / openAskPrice;
            return percentageChange;
        }

        /**
         * Obtains the change in ask price since last price.
         * 
         * @return Change in ask price since last price.
         */
        public double getLastPriceChange() {
            return (this.currentPrice.getAskPrice() - this.lastPrice.getAskPrice());
        }

        /**
         * Obtains the {@link StockPrice}.
         * 
         * @return {@link StockPrice}.
         */
        public StockPrice getStockPrice() {
            return this.currentPrice;
        }
    }

    /**
     * Allow CSS styling of the {@link CellTable}.
     */
    private static class StockWatchResource implements Resources, ImageResource {

        /**
         * {@link StockWatchStyle}.
         */
        private final Style style = new StockWatchStyle();

        /*
         * ================== Resources =======================
         */

        @Override
        public ImageResource cellTableFooterBackground() {
            return this;
        }

        @Override
        public ImageResource cellTableHeaderBackground() {
            return this;
        }

        @Override
        public ImageResource cellTableLoading() {
            return this;
        }

        @Override
        public ImageResource cellTableSelectedBackground() {
            return this;
        }

        @Override
        public ImageResource cellTableSortAscending() {
            return this;
        }

        @Override
        public ImageResource cellTableSortDescending() {
            return this;
        }

        @Override
        public Style cellTableStyle() {
            return this.style;
        }

        /*
         * ================== ImageResource ===========================
         */

        @Override
        public String getName() {
            return "";
        }

        @Override
        public int getHeight() {
            return 0;
        }

        @Override
        public int getLeft() {
            return 0;
        }

        @Override
        public SafeUri getSafeUri() {
            return null;
        }

        @Override
        public int getTop() {
            return 0;
        }

        @Override
        public String getURL() {
            return "";
        }

        @Override
        public int getWidth() {
            return 0;
        }

        @Override
        public boolean isAnimated() {
            return false;
        }
    }

    /**
     * Stock watch {@link Style}.
     */
    private static class StockWatchStyle implements Style {

        /**
         * CSS class for <code>th</code>.
         */
        private static final String TH_CSS_CLASS = "StockWatchTh";

        /**
         * CSS class for <code>td</code>.
         */
        private static final String TD_CSS_CLASS = "StockWatchTd";

        /**
         * CSS class for the first column.
         */
        private static final String FIRST_COLUMN_CSS_CLASS = "StockWatchFirstColumn";

        /**
         * CSS class for odd row.
         */
        private static final String ODD_ROW_CSS_CLASS = "StockWatchOddRow";

        /**
         * CSS class for even row.
         */
        private static final String EVEN_ROW_CSS_CLASS = "StockWatchEvenRow";

        /*
         * ===================== Style ==========================
         */

        @Override
        public boolean ensureInjected() {
            return false;
        }

        @Override
        public String getText() {
            return TD_CSS_CLASS;
        }

        @Override
        public String getName() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableCell() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableEvenRow() {
            return EVEN_ROW_CSS_CLASS;
        }

        @Override
        public String cellTableEvenRowCell() {
            return EVEN_ROW_CSS_CLASS;
        }

        @Override
        public String cellTableFirstColumn() {
            return FIRST_COLUMN_CSS_CLASS;
        }

        @Override
        public String cellTableFirstColumnFooter() {
            return FIRST_COLUMN_CSS_CLASS;
        }

        @Override
        public String cellTableFirstColumnHeader() {
            return FIRST_COLUMN_CSS_CLASS;
        }

        @Override
        public String cellTableFooter() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableHeader() {
            return TH_CSS_CLASS;
        }

        @Override
        public String cellTableHoveredRow() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableHoveredRowCell() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableKeyboardSelectedCell() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableKeyboardSelectedRow() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableKeyboardSelectedRowCell() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableLastColumn() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableLastColumnFooter() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableLastColumnHeader() {
            return TH_CSS_CLASS;
        }

        @Override
        public String cellTableLoading() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableOddRow() {
            return ODD_ROW_CSS_CLASS;
        }

        @Override
        public String cellTableOddRowCell() {
            return ODD_ROW_CSS_CLASS;
        }

        @Override
        public String cellTableSelectedRow() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableSelectedRowCell() {
            return TD_CSS_CLASS;
        }

        @Override
        public String cellTableSortableHeader() {
            return TH_CSS_CLASS;
        }

        @Override
        public String cellTableSortedHeaderAscending() {
            return TH_CSS_CLASS;
        }

        @Override
        public String cellTableSortedHeaderDescending() {
            return TH_CSS_CLASS;
        }

        @Override
        public String cellTableWidget() {
            return "StockWatchTable";
        }
    }

}