Java tutorial
/* * WBI Indicator Explorer * * Copyright 2015 Sebastian Nogara <snogaraleal@gmail.com> * * This file is part of WBI. * * 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 client.managers.models; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import com.google.gwt.user.client.Timer; import rpc.client.ClientRequest; import models.Country; import models.Indicator; import models.Point; import models.Series; import client.managers.Manager; import client.services.WBIExplorationService; /** * {@link Manager} in charge of fetching and providing rows of * {@link Series} as needed. * * @see Manager */ public class SeriesManager implements Manager, IntervalManager.Listener, IndicatorManager.Listener, CountryManager.Listener { /** * Interface for views that interact with a {@link SeriesManager}. */ public static interface View extends Manager.View<SeriesManager> { } /** * Wrapper around {@link Series} providing selection and visibility. */ public static class Row { /** * {@code SeriesManager} that created this {@code Row}. */ private SeriesManager manager; /** * Wrapped {@code Series} object. */ private Series series; /** * Whether this row is selected. */ private boolean selected; /** * Whether this row is visible. */ private boolean visible; /** * Initialize {@code Row}. * * @param manager {@code SeriesManager} used to create this row. * @param series Wrapped {@code Series} object. * @param selected Whether this row is selected. * @param visible Whether this row is visible. */ public Row(SeriesManager manager, Series series, boolean selected, boolean visible) { this.manager = manager; this.series = series; this.selected = selected; this.visible = visible; } /** * Initialize {@code Row}. * * @param manager {@code SeriesManager} used to create this row. * @param series Wrapped {@code Series} object. * @param selected Whether this row is selected. */ public Row(SeriesManager manager, Series series, boolean selected) { this(manager, series, selected, true); } /** * Initialize {@code Row}. * * @param manager {@code SeriesManager} used to create this row. * @param series Wrapped {@code Series} object. */ public Row(SeriesManager manager, Series series) { this(manager, series, false); } /** * Get wrapped {@code Series} object. * * @return Wrapped series. */ public Series getSeries() { return series; } /** * Get whether this row is selected. * * @return Whether this row is selected. */ public boolean isSelected() { return selected; } /** * Set whether this row is selected. * * @param selected Whether this row is selected. */ public void setSelected(boolean selected) { this.selected = selected; manager.change(this); } /** * Get whether this row is visible. * * @return Whether this row is visible. */ public boolean isVisible() { return visible; } /** * Set whether this row is visible. * * @param visible Whether this row is visible. */ public void setVisible(boolean visible) { this.visible = visible; manager.change(this); } } /** * Object representing the ordering criteria of a list of * {@link Row} objects. */ public static class Ordering { /** * Whether the order is ascending or descending. */ public static enum Direction { /** * Ascending {@code Direction}. */ ASC, /** * Descending {@code Direction}. */ DESC } /** * Order by country name. */ public static int BY_COUNTRY_NAME = 0; /** * Year by which to order or {@link Ordering#BY_COUNTRY_NAME}. */ private int by; /** * Order direction. */ private Direction direction; /** * Initialize {@code Ordering}. * * @param by Year by which to order or {@link Ordering#BY_COUNTRY_NAME}. * @param direction Ordering {@link Direction}. */ public Ordering(int by, Direction direction) { this.by = by; this.direction = direction; } /** * Initialize {@code Ordering} with default values. */ public Ordering() { this(BY_COUNTRY_NAME, Direction.ASC); } /** * Set year by which to order. * * @param by Year by which to order or {@link Ordering#BY_COUNTRY_NAME}. */ public void setBy(int by) { this.by = by; } /** * Get year by which to order. * * @return Year by which to order or {@link Ordering#BY_COUNTRY_NAME}. */ public int getBy() { return by; } /** * Set current {@link Direction}. * * @param direction Ordering direction. */ public void setDirection(Direction direction) { this.direction = direction; } /** * Get current {@link Direction}. * * @return Ordering direction. */ public Direction getDirection() { return direction; } /** * Get whether the current {@link Direction} is ascending. * * @return Whether the current direction is ascending. */ public boolean isAscending() { return direction == Direction.ASC; } /** * Get whether the current {@link Direction} is descending. * * @return Whether the current direction is descending. */ public boolean isDescending() { return direction == Direction.DESC; } /** * {@code java.util.Comparator} of {@link Row} objects based on the * criteria defined by an {@link Ordering} object. */ public static class Comparator implements java.util.Comparator<Row> { /** * Current ordering criteria. */ private Ordering ordering; /** * Initialize {@code Comparator}. * * @param ordering {@link Ordering} instance. */ public Comparator(Ordering ordering) { this.ordering = ordering; } /** * Compare country names from rows. * * @param a {@code Row} to compare. * @param b {@code Row} to compare. * @return Comparation result. */ private int compareCountryName(Row a, Row b) { Country countryA = a.getSeries().getCountry(); Country countryB = b.getSeries().getCountry(); if (countryA == null && countryB == null) { return 0; } if (countryA == null) { return 1; } if (countryB == null) { return -1; } String nameA = countryA.getName(); String nameB = countryB.getName(); return nameA.compareTo(nameB); } /** * Compare {@code Point} values corresponding to the year * specified in the current {@code Ordering}. * * @param a {@code Row} to compare. * @param b {@code Row} to compare. * @return Comparation result. */ private int compareYear(Row a, Row b) { int year = ordering.getBy(); Double pointA = a.getSeries().getPointValue(year); Double pointB = b.getSeries().getPointValue(year); if (pointA == null && pointB == null) { return 0; } if (pointA == null) { return -1; } if (pointB == null) { return 1; } return pointA.compareTo(pointB); } /** * Compare {@code Row} objects. */ @Override public int compare(Row a, Row b) { int value; if (ordering.getBy() == BY_COUNTRY_NAME) { value = compareCountryName(a, b); } else { value = compareYear(a, b); } switch (ordering.getDirection()) { case ASC: return value; case DESC: return -value; } return value; } } /** * Create {@link Comparator} with the criteria defined by this * {@link Ordering} object. * * @return Created comparator. */ public Comparator createComparator() { return new Comparator(this); } } /** * Interface for serializers of {@link Row} lists. */ public static interface Serializer { /** * Serialize a list of {@link Row} to string. * * @param years List of years the rows contain information about. * @param list List of rows. * @return Serialized value. */ String serialize(SortedSet<Integer> years, List<Row> list); } /** * Interface for filters of {@link Row} lists. */ public static interface Filter { /** * Get whether a {@link Row} matches this filter. * * @param row Row to match. * @return Whether the row matches. */ boolean matches(Row row); } /** * Interface for {@link SeriesManager} listeners. */ public static interface Listener { /** * Handle new list of {@link Row} objects. * * @param rows New list of rows. * @param years List of years the rows contain information about. * @param ordering Current {@link Ordering}. */ void onUpdate(List<Row> rows, SortedSet<Integer> years, Ordering ordering); /** * Handle change in {@link Row} object. * * @param row Changed row. */ void onChange(Row row); } /** * {@code Listener} objects listening to this manager. */ private List<Listener> listeners = new ArrayList<Listener>(); /** * Delay before querying. */ private static final int QUERY_DELAY = 100; /** * {@code ClientRequest.Listener} for query requests. */ private ClientRequest.Listener<List<Series>> queryRequestListener; /** * Timer for scheduling query requests. */ private Timer queryTimer; /** * Current ordering criteria. */ private Ordering ordering = new Ordering(); /** * List of {@code Row} objects from last query. */ private List<Row> rows = new ArrayList<Row>(); /** * {@code Row} objects by {@code Country}. */ private Map<Country, Row> rowsByCountry = new HashMap<Country, Row>(); /** * Set of years that the current list of rows contains information about. */ private SortedSet<Integer> years = new TreeSet<Integer>(); /** * Attached {@code IntervalManager} providing an * {@code IntervalManager.Option}. */ private IntervalManager intervalManager; /** * Attached {@code IndicatorManager} providing an {@code Indicator}. */ private IndicatorManager indicatorManager; /** * Attached {@code CountryManager} providing a {@code Country}. */ private CountryManager countryManager; /** * Initialize {@code SeriesManager}. */ public SeriesManager() { /* * Initialize {@code ClientRequest.Listener} for queries. */ queryRequestListener = new ClientRequest.Listener<List<Series>>() { @Override public void onSuccess(List<Series> series) { load(series); } @Override public void onFailure(ClientRequest.Error error) { } }; /* * Initialize timer for queries. */ queryTimer = new Timer() { @Override public void run() { Indicator indicator = getCurrentIndicatorManager().getSelectedIndicator(); IntervalManager.Option option = getCurrentIntervalManager().getSelectedOption(); if (indicator != null && option != null) { WBIExplorationService.querySeriesList(indicator.getId(), option.getStartYear(), option.getEndYear(), queryRequestListener); } } }; } /** * Serialize rows provided by this {@link SeriesManager}. * * @param serializer {@link Serializer} implementing serialization. * @return Serialized contents. */ public String serialize(Serializer serializer) { return serializer.serialize(years, rows); } /** * Schedule a series query. */ private void scheduleQuery() { queryTimer.cancel(); queryTimer.schedule(QUERY_DELAY); } /** * Attach an {@link IntervalManager}. * * @param manager Manager to attach. */ public void connect(IntervalManager intervalManager) { assert this.intervalManager == null; this.intervalManager = intervalManager; this.intervalManager.addListener(this); } /** * Detach the currently attached {@link IntervalManager}. */ public void disconnectIntervalManager() { assert this.intervalManager != null; this.intervalManager.removeListener(this); this.intervalManager = null; } /** * Get the currently attached {@link IntervalManager}. * * @return Currently attached interval manager. */ public IntervalManager getCurrentIntervalManager() { return intervalManager; } @Override public void onSelect(IntervalManager.Option option) { scheduleQuery(); } /** * Attach an {@link IndicatorManager}. * * @param manager Manager to attach. */ public void connect(IndicatorManager indicatorManager) { assert this.indicatorManager == null; this.indicatorManager = indicatorManager; this.indicatorManager.addListener(this); } /** * Detach the currently attached {@link IndicatorManager}. */ public void disconnectIndicatorManager() { assert this.indicatorManager != null; this.indicatorManager.removeListener(this); this.indicatorManager = null; } /** * Get the currently attached {@link IndicatorManager}. * * @return Currently attached indicator manager. */ public IndicatorManager getCurrentIndicatorManager() { return indicatorManager; } @Override public void onSearch(List<Indicator> indicators, Indicator selectedIndicator) { } @Override public void onChange(Indicator indicator) { } @Override public void onSelect(final Indicator indicator) { scheduleQuery(); } /** * Attach a {@link CountryManager}. * * @param manager Manager to attach. */ public void connect(CountryManager countryManager) { assert this.countryManager == null; this.countryManager = countryManager; this.countryManager.addListener(this); } /** * Detach the currently attached {@link CountryManager}. */ public void disconnectCountryManager() { assert this.countryManager != null; this.countryManager.removeListener(this); this.countryManager = null; } /** * Get the currently attached {@link CountryManager}. * * @return Currently attached country manager. */ public CountryManager getCurrentCountryManager() { return countryManager; } @Override public void onSearch(List<Country> countries, List<Country> selectedCountries) { } @Override public void onAdd(Country country) { Row row = rowsByCountry.get(country); if (row != null) { row.setSelected(true); } } @Override public void onRemove(Country country) { Row row = rowsByCountry.get(country); if (row != null) { row.setSelected(false); } } @Override public void onClear(List<Country> selectedCountries) { for (Country country : selectedCountries) { Row row = rowsByCountry.get(country); if (row != null) { row.setSelected(false); } } } /** * Get list of {@link Row} objects from last query. * * @return List of row objects. */ public List<Row> getRows() { return rows; } /** * Get the set of years that the current list of rows contains * information about. * * @return List of years. */ public SortedSet<Integer> getYears() { return years; } /** * Get the current {@link Ordering}. * * @return Current ordering. */ public Ordering getOrdering() { return ordering; } /** * Attach {@code Listener}. * * @param listener Listener to attach. */ public void addListener(Listener listener) { listener.onUpdate(rows, years, ordering); listeners.add(listener); } /** * Detach {@code Listener}. * * @param listener Listener to detach. */ public void removeListener(Listener listener) { listeners.remove(listener); } /** * Call {@link Listener#onUpdate} on all listeners. */ private void update() { for (Listener listener : listeners) { listener.onUpdate(rows, years, ordering); } } /** * Call {@link Listener#onChange} on all listeners. * * @param row {@link Row} changed. */ private void change(Row row) { for (Listener listener : listeners) { listener.onChange(row); } } /** * Load a list of {@link Series} objects. * * @param seriesList List of series. */ private void load(List<Series> seriesList) { rows.clear(); rowsByCountry.clear(); years.clear(); List<Country> selectedCountries = countryManager.getSelectedCountries(); for (Series series : seriesList) { for (Point point : series.getPoints()) { years.add(point.getYear()); } Country country = series.getCountry(); Row row = new Row(this, series, selectedCountries.contains(country)); rows.add(row); rowsByCountry.put(country, row); } Collections.sort(rows, ordering.createComparator()); update(); } /** * Set the current {@link Ordering}. * * @param ordering Ordering criteria. */ public void setOrdering(Ordering ordering) { this.ordering = ordering; Collections.sort(rows, ordering.createComparator()); update(); } /** * Set the current {@link Filter}. * * @param filter {@code Filter} implementer. */ public void setFilter(Filter filter) { for (Row row : rows) { boolean visible = filter.matches(row); if (visible != row.isVisible()) { row.setVisible(visible); } } } }