package edu.kit.pse.ws2013.routekit.controllers; import java.io.File; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import edu.kit.pse.ws2013.routekit.controllers.ProfileManagerController.ProfilesDiff; import edu.kit.pse.ws2013.routekit.map.StreetMap; import edu.kit.pse.ws2013.routekit.models.ProfileMapCombination; import edu.kit.pse.ws2013.routekit.models.ProgressReporter; import edu.kit.pse.ws2013.routekit.profiles.Profile; import edu.kit.pse.ws2013.routekit.util.FileUtil; import edu.kit.pse.ws2013.routekit.views.MainView; import edu.kit.pse.ws2013.routekit.views.MapManagerView; /** * The controller for the {@link MapManagerView}. */ public class MapManagerController { MapManagerView mmv; private final Map<StreetMap, Set<Profile>> precalculations = new HashMap<>(); private final Map<String, Profile> profilesByName = new HashMap<>(); private final Map<String, StreetMap> mapsByName = new HashMap<>(); private StreetMap currentMap; private StreetMap selectedMap; // set in saveAllChanges public MapManagerController(MainView view) { initPrecalculations(); initNameMaps(); currentMap = ProfileMapManager.getInstance().getCurrentCombination() .getStreetMap(); Set<Profile> profilesForCurrentMap = precalculations.get(currentMap); if (profilesForCurrentMap == null) { profilesForCurrentMap = new HashSet<>(); } mmv = new MapManagerView(view, this, currentMap, MapManager .getInstance().getMaps(), profilesForCurrentMap); mmv.setVisible(true); } private void initPrecalculations() { precalculations.clear(); for (StreetMap map : MapManager.getInstance().getMaps()) { precalculations.put(map, new HashSet<Profile>()); } for (ProfileMapCombination combination : ProfileMapManager .getInstance().getPrecalculations()) { StreetMap map = combination.getStreetMap(); Set<Profile> profiles = precalculations.get(map); profiles.add(combination.getProfile()); precalculations.put(map, profiles); } } /** * (Re-)Initialize {@link #profilesByName} and {@link #mapsByName}. */ private void initNameMaps() { initProfilesByName(); initMapsByName(); } private void initMapsByName() { mapsByName.clear(); for (StreetMap map : MapManager.getInstance().getMaps()) { mapsByName.put(map.getName(), map); } } private void initProfilesByName() { profilesByName.clear(); for (Profile profile : ProfileManager.getInstance().getProfiles()) { profilesByName.put(profile.getName(), profile); } } /** * Removes the current map. * <p> * (Note that, as with all other changes, this is only propagated to the * application’s model in {@link #saveAllChanges()}.) * <p> * If the current map {@link StreetMap#isDefault() is a default map}, an * {@link IllegalStateException} is thrown. */ public void deleteCurrentMap() { if (currentMap.isDefault()) { throw new IllegalStateException("Can’t delete a default map!"); } precalculations.remove(currentMap); mapsByName.remove(currentMap.getName()); Set<StreetMap> availableMaps = precalculations.keySet(); currentMap = availableMaps.iterator().next(); mmv.setAvailableMaps(availableMaps); mmv.setCurrentMap(currentMap, precalculations.get(currentMap)); } /** * Removes the given profile from the current map. * * @param profile * The profile that shall be removed. */ public void removeProfile(String profileName) { Profile removedProfile = profilesByName.get(profileName); if (removedProfile == null) { throw new IllegalArgumentException("Profile with name '" + removedProfile + "' does not exist!"); } Set<Profile> profiles = precalculations.get(currentMap); profiles.remove(removedProfile); mmv.setCurrentMap(currentMap, profiles); } /** * Performs all changes that the user requested. * <ul> * <li>imports new and changed maps</li> * <li>deletes removed maps</li> * <li>deletes removed precalculations</li> * <li>performs requested precalculations</li> * </ul> * <p> * The given {@link ProgressReporter} should already have the task * "Saving changes" or something similar pushed onto it. This method will * then push and pop sub-tasks. * <p> * The changes are executed asynchronously in a new worker thread, and after * all changes have been executed, an additional task is popped off the * reporter that this method did not push (the task "Saving changes", as * mentioned earlier). This way, the caller may be notified when the changes * are done. * * @param reporter * The {@link ProgressReporter} to report progress to. */ public void saveAllChanges(final ProgressReporter reporter) { new Thread("MapManagerController Worker Thread") { { setDaemon(true); } @Override public void run() { final ManagementActions changes = getChanges(); selectedMap = changes.execute(currentMap, reporter); reporter.popTask(); // pop root task } }.start(); } /** * Opens a Profile Management Dialog to select a profile to add to the list * of precalculations for the current map. * * @see ProfileManagerController */ public void addProfile() { ProfileManagerController c = new ProfileManagerController(mmv); Profile addedProfile = c.getSelectedProfile(); if (addedProfile != null) { ProfilesDiff diff = c.diff(); for (Set<Profile> p : precalculations.values()) { Map<Profile, Profile> changed = new HashMap<>(); for (Profile pr : p) { for (Profile q : diff.getChangedProfiles()) { if (pr.getName().equals(q.getName())) { changed.put(pr, q); break; } } } for (Entry<Profile, Profile> e : changed.entrySet()) { p.remove(e.getKey()); p.add(e.getValue()); } p.removeAll(diff.getDeletedProfiles()); } // the ProfileManager can delete precalculations and add profiles initProfilesByName(); Set<Profile> profiles = precalculations.get(currentMap); if (profiles == null) { profiles = new HashSet<>(); } profiles.add(addedProfile); precalculations.put(currentMap, profiles); mmv.setCurrentMap(currentMap, profiles); } } /** * Adds a new map with the given name, remembers to load it from the given * file in {@link #saveAllChanges()}, and selects it. * <p> * Note that the two actions “Import” and “Update”, which are separate in * the GUI, are both performed through this method. For the first action, * the GUI must ensure that the name is unused, while for the second action * an existing name is given. * * @param name * The name of the new map. * @param file * The file from which it should be loaded (later). * @throws FileNotFoundException * If the file doesn’t exists. * @throws IllegalArgumentException * If the map name is {@link MapManager#checkMapName(String) * invalid}. */ public void importMap(String name, File file) throws FileNotFoundException { if (!FileUtil.checkMapName(name)) { throw new IllegalArgumentException("Invalid map name!"); } if (!file.exists()) { throw new FileNotFoundException(); } currentMap = new FutureMap(name, file); StreetMap previousMap = mapsByName.get(name); Set<Profile> profiles; if (previousMap == null) { profiles = new HashSet<>(); } else { profiles = precalculations.get(previousMap); precalculations.remove(previousMap); } mapsByName.put(name, currentMap); precalculations.put(currentMap, profiles); mmv.setAvailableMaps(precalculations.keySet()); mmv.setCurrentMap(currentMap, precalculations.get(currentMap)); } /** * Switch to the map with the given name. * * @param map * The name of the new map. */ public void changeMap(String mapName) { StreetMap newMap = mapsByName.get(mapName); if (newMap == null) { throw new IllegalArgumentException("StreetMap with name '" + mapName + "' does not exist!"); } currentMap = newMap; Set<Profile> profiles = precalculations.get(currentMap); if (profiles == null) { profiles = new HashSet<>(); } mmv.setCurrentMap(currentMap, profiles); } protected MapManagerView getView() { return mmv; } /** * Returns the map that the user selected. Note that this is different from * the <i>current</i> map (which is the one that the user has currently * selected, while the view is still visible); the selected map is only set * in {@link #saveAllChanges()}, and if that method is never called * (e. g. because the user clicked “Cancel”), then this method returns * {@code null} to indicate that. * * @return The map that the user selected. */ public StreetMap getSelectedMap() { return selectedMap; } /** * Gets all changes that the user requested, that is, the changes that * {@link #saveAllChanges()} would execute if called right now. * * @return All changes. */ public ManagementActions getChanges() { MapManager mapManager = MapManager.getInstance(); ProfileMapManager profileMapManager = ProfileMapManager.getInstance(); return mapManagementDiff(mapManager.getMaps(), profileMapManager.getPrecalculations(), precalculations); } private static ManagementActions mapManagementDiff(Set<StreetMap> maps, Set<ProfileMapCombination> precalculations, Map<StreetMap, Set<Profile>> changes) { final Set<FutureMap> newOrUpdatedMaps; final Set<StreetMap> deletedMaps; final Set<ProfileMapCombination> deletedPrecalculations; final Set<ProfileMapCombination> newPrecalculations; deletedMaps = new HashSet<>(); for (StreetMap map : maps) { if (!changes.containsKey(map)) { deletedMaps.add(map); } } newOrUpdatedMaps = new HashSet<>(); final Map<String, StreetMap> mapsByName = new HashMap<>(); for (StreetMap map : maps) { mapsByName.put(map.getName(), map); } for (StreetMap map : changes.keySet()) { StreetMap existing = mapsByName.get(map.getName()); if (existing == null || map != existing) { assert (map instanceof FutureMap); newOrUpdatedMaps.add((FutureMap) map); } } deletedPrecalculations = new HashSet<>(); for (ProfileMapCombination precalculation : precalculations) { StreetMap map = precalculation.getStreetMap(); if (deletedMaps.contains(map) || newOrUpdatedMaps.contains(map) || !changes.get(map).contains(precalculation.getProfile())) { deletedPrecalculations.add(precalculation); } } newPrecalculations = new HashSet<>(); final Map<StreetMap, Set<Profile>> precalculationsAsMap = new HashMap<>(); for (ProfileMapCombination precalculation : precalculations) { StreetMap map = precalculation.getStreetMap(); Set<Profile> profiles = precalculationsAsMap.get(map); if (profiles == null) { profiles = new HashSet<>(); } profiles.add(precalculation.getProfile()); precalculationsAsMap.put(map, profiles); } for (Entry<StreetMap, Set<Profile>> combinationSet : changes.entrySet()) { StreetMap map = combinationSet.getKey(); for (Profile profile : combinationSet.getValue()) { Set<Profile> profiles = precalculationsAsMap.get(map); if (profiles == null) { profiles = new HashSet<>(); } if (newOrUpdatedMaps.contains(map) || !profiles.contains(profile)) { newPrecalculations.add(new ProfileMapCombination(map, profile)); } } } return new ManagementActions(newOrUpdatedMaps, deletedMaps, deletedPrecalculations, newPrecalculations); } } class FutureMap extends StreetMap { private final File osmFile; public FutureMap(String name, File osmFile) { super(null, null); this.name = name; this.osmFile = osmFile; } @Override public boolean isDefault() { return false; } public File getOsmFile() { return osmFile; } @Override public boolean equals(Object obj) { return this == obj; } }