/* * 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.history; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gwt.core.client.Callback; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import rpc.client.ClientRequest; import rpc.shared.data.Serializable; import rpc.shared.data.Type; import client.services.WBIExplorationService; /** * Browser history state. * * @see HistoryManager */ public class HistoryState implements Serializable { /** * Start year of the visualized {@code IntervalManager.Option}. */ private Integer intervalStartYear; /** * End year of the visualized {@code IntervalManager.Option}. */ private Integer intervalEndYear; /** * Name of the selected tab from the series tab panel. */ private String seriesTabName; /** * Identifier of the selected {@code Indicator}. */ private String indicatorIdent; /** * List of ISO codes from the selected {@code Country} objects. */ private List<String> countryISOList = new ArrayList<String>(); /* * The following fields relate to {@code HistoryStateData} retrieval. */ /** * {@code HistoryStateData} that represents the values of this * {@code HistoryState} or {@code null} if it must be retrieved. */ private HistoryStateData data; /** * Whether a {@code HistoryStateData} is being retrieved. */ private boolean loadingData; /** * {@code Callback} objects awaiting a {@code HistoryStateData}. */ private List<Callback<HistoryStateData, Void>> dataCallbacks = new ArrayList<Callback<HistoryStateData, Void>>(); /** * Initialize {@code HistoryState}. */ public HistoryState() { invalidateData(); } /** * Initialize {@code HistoryState} with specific information about the * current state. * * @param intervalStartYear Start year of the visualized interval. * @param intervalEndYear End year of the visualized interval. * @param seriesTabName Name of the selected tab from the series panel. * @param indicatorIdent Identifier of the current indicator. * @param countryISOList List of ISO codes from the selected countries. */ public HistoryState( Integer intervalStartYear, Integer intervalEndYear, String seriesTabName, String indicatorIdent, List<String> countryISOList) { this(); setInterval(intervalStartYear, intervalEndYear); setSeriesTabName(seriesTabName); setIndicatorIdent(indicatorIdent); setCountryISOList(countryISOList); } /** * Regular expression representing the history token format. */ public static final RegExp REGEX = RegExp.compile("([0-9]*):([0-9]*)/([^/]*)/?([^/]*)/?((.{2},?)*)"); private static final String COUNTRY_ISO_LIST_SEPARATOR = ","; private static final int REGEX_INTERVAL_START_YEAR = 1; private static final int REGEX_INTERVAL_END_YEAR = 2; private static final int REGEX_SERIES_TAB_NAME = 3; private static final int REGEX_INDICATOR_IDENT = 4; private static final int REGEX_COUNTRY_ISO_LIST = 5; /** * Create a {@code HistoryState} from a history token. * * <pre>([0-9]*) : ([0-9]*) / ([^/]*) /? ([^/]*) /? ((.{2},?)*)</pre> * * <ol> * <li>Interval start</li> * <li>Interval end</li> * <li>Series tab name</li> * <li>Indicator identifier (optional)</li> * <li>Comma-separated list of ISO codes (optional)</li> * </ol> * * Examples: * * <ul> * <li>2010:2014/map:europe/ABC.DEF.1234/SE,DK,NO</li> * <li>2010:2014/map:europe/ABC.DEF.1234</li> * <li>2010:2014/map:europe</li> * </ul> * * @param historyToken History token. * @return Created {@code HistoryState} instance. * * @see HistoryState#REGEX */ public static HistoryState fromHistoryToken(String historyToken) { MatchResult result = REGEX.exec(historyToken); if (result == null || result.getGroupCount() <= 1) { return new HistoryState(); } /* * Interval */ Integer intervalStartYear = Integer.valueOf(result.getGroup(REGEX_INTERVAL_START_YEAR)); Integer intervalEndYear = Integer.valueOf(result.getGroup(REGEX_INTERVAL_END_YEAR)); /* * Tab name */ String seriesTabName = result.getGroup(REGEX_SERIES_TAB_NAME); if (seriesTabName != null) { seriesTabName = seriesTabName.trim(); if (seriesTabName.isEmpty()) { seriesTabName = null; } } /* * Indicator */ String indicatorIdent = result.getGroup(REGEX_INDICATOR_IDENT); if (indicatorIdent != null) { indicatorIdent = indicatorIdent.trim(); if (indicatorIdent.isEmpty()) { indicatorIdent = null; } } /* * Country ISO list */ String countryISOListString = result.getGroup(REGEX_COUNTRY_ISO_LIST); List<String> countryISOList = null; if (countryISOListString != null) { countryISOListString = countryISOListString.trim(); if (!countryISOListString.isEmpty()) { countryISOList = new ArrayList<String>(Arrays.asList( countryISOListString.split(COUNTRY_ISO_LIST_SEPARATOR))); } } return new HistoryState( intervalStartYear, intervalEndYear, seriesTabName, indicatorIdent, countryISOList); } /** * Whether this {@code HistoryState} is missing information. * * @return Whether the state is missing information. */ public boolean isEmpty() { return intervalStartYear == null || intervalEndYear == null || seriesTabName == null; } /** * Generate a history token with the information in this * {@code HistoryState}. * * @return History token. */ public String getHistoryToken() { if (isEmpty()) { return null; } /* * Interval and tab name */ String historyToken = intervalStartYear + ":" + intervalEndYear + "/" + seriesTabName; if (indicatorIdent != null) { /* * Indicator */ historyToken += "/" + indicatorIdent; /* * Country ISO list */ if (countryISOList != null && !countryISOList.isEmpty()) { historyToken += "/"; boolean needsComma = false; for (String iso : countryISOList) { if (needsComma) { historyToken += ","; } else { needsComma = true; } historyToken += iso; } } } return historyToken; } /** * Get start year of the visualized interval. * * @return Interval start year. */ public Integer getIntervalStartYear() { return intervalStartYear; } /** * Get end year of the visualized interval. * * @return Interval end year. */ public Integer getIntervalEndYear() { return intervalEndYear; } /** * Set start and end of the visualized interval. * * @param startYear Start year of the interval. * @param endYear End year of the interval. */ public void setInterval(Integer startYear, Integer endYear) { this.intervalStartYear = startYear; this.intervalEndYear = endYear; } /** * Get the name of the selected series tab. * * @return Selected tab name. */ public String getSeriesTabName() { return seriesTabName; } /** * Set the name of the selected series tab. * * @param seriesTabName Tab name. */ public void setSeriesTabName(String seriesTabName) { this.seriesTabName = seriesTabName; } /** * Get identifier of the selected indicator. * * @return Indicator identifier. */ public String getIndicatorIdent() { return indicatorIdent; } /** * Set the identifier of the selected indicator. * * @param indicatorIdent Indicator identifier. */ public void setIndicatorIdent(String indicatorIdent) { if (this.indicatorIdent != null && this.indicatorIdent.equals(indicatorIdent)) { return; } this.indicatorIdent = indicatorIdent; invalidateData(); } /** * Get the list of ISO codes corresponding to selected countries. * * @return List of ISO codes. */ public List<String> getCountryISOList() { return countryISOList; } /** * Set the list of ISO codes corresponding to selected countries. * * @param countryISOList List of ISO codes. */ public void setCountryISOList(List<String> countryISOList) { if (countryISOList == null) { countryISOList = new ArrayList<String>(); } if (this.countryISOList != null) { // Remove duplicates Set<String> countryISOSet = new HashSet<String>(countryISOList); countryISOList.clear(); countryISOList.addAll(countryISOSet); // Get ISO codes that are not in the new list List<String> toRemoveCountryISOList = new ArrayList<String>(this.countryISOList); toRemoveCountryISOList.removeAll(countryISOList); // Return if there are no differences between the lists if (this.countryISOList.size() == countryISOList.size() && toRemoveCountryISOList.isEmpty()) { return; } } this.countryISOList = countryISOList; invalidateData(); } /** * Replace the information in this {@code HistoryState} with information * from a different {@code HistoryState}. * * @param state History state with new values. */ public void replace(HistoryState state) { setInterval(state.getIntervalStartYear(), state.getIntervalEndYear()); setSeriesTabName(state.getSeriesTabName()); setIndicatorIdent(state.getIndicatorIdent()); setCountryISOList(state.getCountryISOList()); } /* * The following methods relate to {@code HistoryStateData} retrieval. */ /** * Invalidate the current {@code HistoryStateData}. */ private void invalidateData() { this.loadingData = false; this.data = null; } /** * Set the current {@code HistoryStateData}. * * @param data Up-to-date state data. */ private void setData(HistoryStateData data) { this.loadingData = false; this.data = data; } /** * Request an up-to-date {@code HistoryStateData}. * * @param callback {@code Callback} called when ready. */ public void getData(final Callback<HistoryStateData, Void> callback) { if (data != null) { callback.onSuccess(data); return; } dataCallbacks.add(callback); if (loadingData) { return; } loadingData = true; /* * Make RPC request */ WBIExplorationService.getStateData( this, new ClientRequest.Listener<HistoryStateData>() { @Override public void onSuccess(HistoryStateData data) { setData(data); // Call callbacks for (Callback<HistoryStateData, Void> callback : dataCallbacks) { callback.onSuccess(data); } dataCallbacks.clear(); } @Override public void onFailure(ClientRequest.Error error) { invalidateData(); // Call callbacks for (Callback<HistoryStateData, Void> callback : dataCallbacks) { callback.onFailure(null); } dataCallbacks.clear(); } }); } /* * {@code Serializable} implementation */ public static final String FIELD_INDICATOR_IDENT = "indicator"; public static final String FIELD_COUNTRY_ISO_LIST = "countries"; @Override public Object get(String field) { if (field.equals(FIELD_INDICATOR_IDENT)) return indicatorIdent; if (field.equals(FIELD_COUNTRY_ISO_LIST)) return countryISOList; return null; } @SuppressWarnings("unchecked") @Override public void set(String field, Object value) { if (field.equals(FIELD_INDICATOR_IDENT)) { indicatorIdent = (String) value; } if (field.equals(FIELD_COUNTRY_ISO_LIST)) { countryISOList = (List<String>) value; } } private static Map<String, Type> fields; @Override public Map<String, Type> fields() { if (fields == null) { fields = new HashMap<String, Type>(); fields.put(FIELD_INDICATOR_IDENT, Type.get(String.class)); fields.put( FIELD_COUNTRY_ISO_LIST, Type.get(List.class, Type.get(String.class))); } return fields; } }