package com.schneeloch.bostonbusmap_library.data; import java.io.IOException; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import android.os.RemoteException; import android.util.Log; import com.schneeloch.bostonbusmap_library.provider.IDatabaseAgent; import com.schneeloch.bostonbusmap_library.transit.ITransitSystem; public class RoutePool extends Pool<String, RouteConfig> { private final IDatabaseAgent databaseAgent; private final ConcurrentMap<String, StopLocation> sharedStops = Maps.newConcurrentMap(); /** * A mapping of stop key to route key. Look in sharedStops for the StopLocation */ private final CopyOnWriteArraySet<String> favoriteStops = Sets.newCopyOnWriteArraySet(); private final ConcurrentMap<String, IntersectionLocation> intersections = Maps.newConcurrentMap(); private final ITransitSystem transitSystem; private float maximumDistanceFromIntersection; private boolean filterStopsFromIntersection; public RoutePool(IDatabaseAgent databaseAgent, ITransitSystem transitSystem) { super(50); this.databaseAgent = databaseAgent; this.transitSystem = transitSystem; //TODO: define these as settings maximumDistanceFromIntersection = 1.0f; filterStopsFromIntersection = true; populateFavorites(); populateIntersections(); } /** * If you upgraded, favoritesStops.values() only has nulls. Use the information from the database to figure out * what routes each stop is in. * * Set the favorite status for all StopLocation favorites, and make sure they persist in the route pool. */ public void fillInFavoritesRoutes() { Map<String, StopLocation> stops = getStops(favoriteStops); for (String stop : stops.keySet()) { StopLocation stopLocation = stops.get(stop); if (stopLocation != null) { Log.v("BostonBusMap", "setting favorite status to true for " + stop); stopLocation.setFavorite(Favorite.IsFavorite); sharedStops.put(stop, stopLocation); } } } private Map<String, StopLocation> getStops(AbstractCollection<String> stopTags) { if (stopTags.size() == 0) { return Collections.emptyMap(); } ConcurrentMap<String, StopLocation> ret = Maps.newConcurrentMap(); ArrayList<String> stopTagsToRetrieve = new ArrayList<String>(); for (String stopTag : stopTags) { StopLocation stop = sharedStops.get(stopTag); if (stop == null) { stopTagsToRetrieve.add(stopTag); } else { ret.put(stopTag, stop); } } databaseAgent.getStops(ImmutableList.copyOf(stopTagsToRetrieve), transitSystem, ret); if (ret != null) { sharedStops.putAll(ret); } return ret; } protected RouteConfig create(String routeToUpdate) throws IOException { return databaseAgent.getRoute(routeToUpdate, sharedStops, transitSystem); } private void populateFavorites() { databaseAgent.populateFavorites(favoriteStops); fillInFavoritesRoutes(); } private void populateIntersections() { databaseAgent.populateIntersections(intersections, transitSystem, sharedStops, maximumDistanceFromIntersection, filterStopsFromIntersection); } protected void clearAll() { super.clearAll(); favoriteStops.clear(); sharedStops.clear(); intersections.clear(); } public ImmutableList<StopLocation> getFavoriteStops() { ImmutableList.Builder<StopLocation> ret = ImmutableList.builder(); for (String stopTag : favoriteStops) { StopLocation stopLocation = sharedStops.get(stopTag); if (stopLocation != null) { ret.add(stopLocation); } } return ret.build(); } public Favorite isFavorite(StopLocation location) { return favoriteStops.contains(location.getStopTag()) ? Favorite.IsFavorite : Favorite.IsNotFavorite; } public Favorite setFavorite(StopLocation location, Favorite isFavorite) throws RemoteException { Collection<String> stopTags = databaseAgent.getAllStopTagsAtLocation(location.getStopTag()); databaseAgent.saveFavorite(stopTags, isFavorite); favoriteStops.clear(); populateFavorites(); if (isFavorite == Favorite.IsNotFavorite) { //make sure setFavorite(false) is called for each stop for (String tag : stopTags) { StopLocation stop = sharedStops.get(tag); if (stop != null) { stop.setFavorite(Favorite.IsNotFavorite); } } } return isFavorite; } public boolean addIntersection(IntersectionLocation.Builder build) { boolean success = databaseAgent.addIntersection(build, transitSystem.getRouteKeysToTitles()); if (success) { populateIntersections(); } return success; } public ConcurrentMap<String, StopLocation> getAllStopTagsAtLocation(String stopTag) { ImmutableList<String> tags = databaseAgent.getAllStopTagsAtLocation(stopTag); ConcurrentMap<String, StopLocation> outputMapping = Maps.newConcurrentMap(); databaseAgent.getStops(tags, transitSystem, outputMapping); return outputMapping; } public void clearRecentlyUpdated() { for (StopLocation stop : sharedStops.values()) { stop.clearRecentlyUpdated(); } for (RouteConfig route : values()) { for (StopLocation stop : route.getStopMapping().values()) { stop.clearRecentlyUpdated(); } } } private static class ClosestCacheKey { private final double lat; private final double lon; private final int maxStops; private final Set<String> routes; private final boolean usesRoutes; public ClosestCacheKey(double lat, double lon, int maxStops, Set<String> routes, boolean usesRoutes) { this.lat = lat; this.lon = lon; this.maxStops = maxStops; this.routes = routes; this.usesRoutes = usesRoutes; } public boolean equals(double lat, double lon, int maxStops, Set<String> routes, boolean usesRoutes) { return this.usesRoutes == usesRoutes && this.lat == lat && this.lon == lon && this.maxStops == maxStops && this.routes.equals(routes); } } private ClosestCacheKey previousKey; private Collection<StopLocation> previousValue; public Collection<StopLocation> getClosestStops(double centerLatitude, double centerLongitude, int maxStops) { return databaseAgent.getClosestStops(centerLatitude, centerLongitude, transitSystem, sharedStops, maxStops); } public IntersectionLocation getIntersection(String name) { if (name == null) { return null; } else { return intersections.get(name); } } public boolean hasIntersection(String name) { if (name == null) { return false; } else { return intersections.containsKey(name); } } public void removeIntersection(String name) { databaseAgent.removeIntersection(name); intersections.remove(name); } public void editIntersectionName(String oldName, String newName) { databaseAgent.editIntersectionName(oldName, newName); intersections.remove(oldName); populateIntersections(); } public Collection<String> getIntersectionNames() { return intersections.keySet(); } public Collection<IntersectionLocation> getIntersections() { return intersections.values(); } public ITransitSystem getTransitSystem() { return transitSystem; } /** * Update stops in route and database * @param route * @param stops All stops in the route * @throws IOException */ public void replaceStops(String route, ImmutableMap<String, StopLocation> stops) throws IOException { ImmutableMap.Builder<String, StopLocation> updatedStopsBuilder = ImmutableMap.builder(); ImmutableMap.Builder<String, StopLocation> allStopsBuilder = ImmutableMap.builder(); for (Map.Entry<String, StopLocation> entry : stops.entrySet()) { StopLocation newStop = entry.getValue(); if (sharedStops.containsKey(entry.getKey())) { // sharedStops.put(entry.getKey(), entry.getValue()); StopLocation sharedStop = sharedStops.get(entry.getKey()); if (sharedStop.getLatitudeAsDegrees() == newStop.getLatitudeAsDegrees() && sharedStop.getLongitudeAsDegrees() == newStop.getLongitudeAsDegrees() && sharedStop.getTitle().equals(newStop.getTitle())) { // hasn't changed, leave it alone allStopsBuilder.put(sharedStop.getStopTag(), sharedStop); } else { // something about stop is updated, replace it in sharedStops and queue it for // update in database sharedStops.put(newStop.getStopTag(), newStop); updatedStopsBuilder.put(entry); allStopsBuilder.put(entry); } } else { // new stop, update it in database sharedStops.put(newStop.getStopTag(), newStop); updatedStopsBuilder.put(entry); allStopsBuilder.put(entry); } } ImmutableMap<String, StopLocation> updatedStops = updatedStopsBuilder.build(); // Set favorite status for new or updated stops for (Map.Entry<String, StopLocation> entry : updatedStops.entrySet()) { StopLocation stop = entry.getValue(); stop.setFavorite(isFavorite(stop)); } RouteConfig routeConfig = get(route); routeConfig.replaceStops(allStopsBuilder.build()); databaseAgent.replaceStops(updatedStops.values()); } }