blob: 9db90222e3caab24c49bfa07462763a1a0be89b3 [file] [log] [blame]
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.silvercomet.client;
import com.google.gwt.xhr.client.XMLHttpRequest;
import elemental.client.Browser;
import elemental.html.Window;
import elemental.util.ArrayOf;
import elemental.util.ArrayOfInt;
import elemental.util.ArrayOfString;
import elemental.util.MapFromStringTo;
import elemental.json.Json;
import elemental.js.util.StringUtil;
import elemental.js.util.Xhr;
import elemental.util.CanCompare;
import elemental.dom.TimeoutHandler;
import elemental.util.Collections;
/**
* A very simple data model for the application.
*
* @author knorton@google.com (Kelly Norton)
*/
public class Model implements Xhr.Callback {
/**
* A listener to receive callbacks on model events.
*/
public interface Listener {
void modelDidFailLoading(Model model);
void modelDidFinishBuildingIndex(Model model);
void modelDidFinishLoading(Model model);
}
private static final String DATA_URL = "results.json";
public static final int SECONDS_PER_HISTOGRAM_BUCKET = 600;
/**
* Compute a histogram with number of finishers per bucket of time where the
* size of the bucket is indicated by <code>seconds</code>.
*/
private static ArrayOfInt computeHistogram(ArrayOf<Runner> runners, int seconds) {
final ArrayOfInt hist = Collections.arrayOfInt();
for (int i = 0, n = runners.length(); i < n; ++i) {
int index = runners.get(i).time() / seconds;
hist.set(index, hist.isSet(index) ? hist.get(index) + 1 : 1);
}
int sum = 0;
for (int i = 0, n = hist.length(); i < n; ++i) {
if (hist.isSet(i)) {
sum += hist.get(i);
}
hist.set(i, sum);
}
return hist;
}
/**
* Sorts the list of runners by {@link Runner#time()} and updates their places
* accordingly.
*/
private static ArrayOf<Runner> normalize(ArrayOf<Runner> runners) {
// Sort by time() which is based on bib time.
runners.sort(new CanCompare<Runner>() {
@Override
public int compare(Runner a, Runner b) {
return a.time() - b.time();
}
});
// Update the runner's new place.
for (int i = 0, n = runners.length(); i < n; ++i) {
runners.get(i).setPlace(i + 1);
}
return runners;
}
/**
* Update the model's index with all possible prefixes of the search key.
*/
private static void updateIndexForAllPrefixes(
MapFromStringTo<ArrayOf<Runner>> index, String key, Runner runner) {
assert key.length() > 0 : "key.length must be > 0.";
for (int i = 1, n = key.length(); i <= n; ++i) {
final String prefix = key.substring(0, i);
if (!index.hasKey(prefix)) {
index.put(prefix, Collections.<Runner>arrayOf());
}
final ArrayOf<Runner> values = index.get(prefix);
// Do not add the same runner twice.
if (values.get(values.length() - 1) != runner) {
index.get(prefix).push(runner);
}
}
}
private final Listener listener;
private ArrayOfInt histogram = null;
private MapFromStringTo<ArrayOf<Runner>> index = Collections.mapFromStringTo();
/**
* Create a new model.
*/
public Model(Listener listener) {
this.listener = listener;
}
/**
* Get a reference to the models histogram.
*/
public ArrayOfInt histogram() {
return histogram;
}
/**
* Load the remote data into the model.
*/
public void load() {
Xhr.get(DATA_URL, this);
}
/**
* Called if the XHR fails to load data from there server.
*/
@Override
public void onFail(XMLHttpRequest xhr) {
listener.modelDidFailLoading(this);
}
/**
* Called when XHR successfully loads data from the server.
*/
@Override
public void onSuccess(XMLHttpRequest xhr) {
update((ArrayOf<Runner>)Json.parse(xhr.getResponseText()));
listener.modelDidFinishLoading(this);
}
/**
* Performs a search and returns the list of runners that match.
*/
public ArrayOf<Runner> search(String query) {
return index.get(query);
}
/**
* Update the model's internal data from an list of runners coming from the
* server.
*/
private void update(ArrayOf<Runner> data) {
// Sort & mutate source data.
final ArrayOf<Runner> runners = normalize(data);
// Update indexes later.
Browser.getWindow().setTimeout(new TimeoutHandler() {
@Override
public void onTimeoutHandler() {
for (int i = 0, n = runners.length(); i < n; ++i) {
final Runner runner = runners.get(i);
final String name = runner.name().toLowerCase();
updateIndexForAllPrefixes(index, name, runner);
final ArrayOfString words = StringUtil.split(name, " ");
for (int j = 0, m = words.length(); j < m; ++j) {
updateIndexForAllPrefixes(index, words.get(j), runner);
}
updateIndexForAllPrefixes(index, "" + runner.place(), runner);
updateIndexForAllPrefixes(index, "" + runner.bibNumber(), runner);
}
listener.modelDidFinishBuildingIndex(Model.this);
}
}, 0);
// Compute histogram.
final ArrayOfInt histogram = computeHistogram(runners, SECONDS_PER_HISTOGRAM_BUCKET);
// Update fields.
this.histogram = histogram;
}
}