package edu.kit.pse.ws2013.routekit.controllers; import java.awt.Window; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; 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.views.ProfileManagerView; /** * The controller for the {@link ProfileManagerView}. */ public class ProfileManagerController { private final ProfileManagerView pmv; private final Map<String, Profile> profiles; // note: contents of the map // are not final at all private final Set<ProfileMapCombination> combinationsAtStartup; private Profile currentProfile; private Profile selectedProfile; // set in saveAllChanges private ProfilesDiff diff = null; public ProfileManagerController(final Window parent) { combinationsAtStartup = new HashSet<>(ProfileMapManager.getInstance() .getPrecalculations()); profiles = new HashMap<>(); for (final Profile p : ProfileManager.getInstance().getProfiles()) { profiles.put(p.getName(), p); } assert (!profiles.isEmpty()); Profile p = ProfileMapManager.getInstance().getCurrentCombination() .getProfile(); assert (p != null); currentProfile = p; pmv = new ProfileManagerView(parent, this, p, new HashSet<>( profiles.values())); pmv.setVisible(true); } private void setAvailableProfiles() { assert (!profiles.isEmpty()); pmv.setAvailableProfiles(new HashSet<>(profiles.values())); } /** * Switches to the temporary profile with the given name. If no temporary * profile with the given name exists, it is created as a * {@link Profile#clone() copy} of the current profile. * <p> * The View is informed about this change via * {@link ProfileManagerView#setCurrentProfile}. * * @param name * The name of the new profile. * @throws IllegalArgumentException * If the profile name is * {@link ProfileManager#checkProfileName(String) invalid}. */ public void changeTemporaryProfile(final String name) { Profile p = profiles.get(name); if (p == null) { if (!ProfileManager.getInstance().checkProfileName(name)) { throw new IllegalArgumentException("Invalid profile name!"); } p = currentProfile.clone(); p.setName(name); profiles.put(name, p); setAvailableProfiles(); } setCurrentProfile(p); } private void setCurrentProfile(Profile p) { assert (p != null); currentProfile = p; pmv.setCurrentProfile(p); } /** * Marks the currently selected profile for deletion and removes it from the * selection list. * <p> * Profile Note that the profile is only actually deleted in * {@link ProfileManagerController#saveAllChanges}. * <p> * If the currently selected profile is a {@link Profile#isDefault() default * profile}, an {@code IllegalStateException} is thrown. */ public void deleteCurrentTemporaryProfile() { if (currentProfile.isDefault()) { throw new IllegalStateException("Can’t delete a default profile!"); } profiles.remove(currentProfile.getName()); setAvailableProfiles(); changeTemporaryProfile(profiles.values().iterator().next().getName()); } /** * Saves the values of the temporary profile. Usually called before * {@link ProfileManagerController#changeTemporaryProfile}. * * @param profile * The temporary profile with the currently entered values. * @throws IllegalArgumentException * If the profile is a {@link Profile#isDefault()} profile. */ public void saveTemporaryProfile(final Profile profile) { Profile p = profiles.get(profile.getName()); if (p != null && p.isDefault()) { if (p.equals(profile)) { return; } throw new IllegalArgumentException( "Can’t update a default profile!"); } profiles.put(profile.getName(), profile); if (profile.getName().equals(currentProfile.getName())) { currentProfile = profile; } } /** * Executes all requested changes – the adding, changing and deleting of * profiles. For changed profiles, all precalculations are deleted. * <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", * mentioned earlier). This way, the caller may be notified when the changes * are done. * * @param reporter * The {@link ProgressReporter} to report progress to. * @see ProfileManager#saveProfile(Profile) * @see ProfileManager#deleteProfile(Profile) */ public void saveAllChanges(final ProgressReporter reporter) { new Thread("ProfileManagerController Worker Thread") { { setDaemon(true); } @Override public void run() { selectedProfile = currentProfile; reporter.setSubTasks(new float[] { .1f, .4f, .5f }); reporter.pushTask("Ermittle Änderungen"); diff = diff(); reporter.popTask(); final ProfileManager manager = ProfileManager.getInstance(); reporter.pushTask("Lösche Profile"); reporter.setSubTasks(diff.getDeletedProfiles().size()); for (final Profile deleted : diff.getDeletedProfiles()) { reporter.pushTask("Lösche Profil '" + deleted.getName() + "'"); manager.deleteProfile(deleted); reporter.popTask(); } reporter.nextTask("Speichere Profile"); reporter.setSubTasks(diff.getChangedProfiles().size()); for (final Profile changed : diff.getChangedProfiles()) { try { reporter.pushTask("Speichere Profil '" + changed.getName() + "'"); manager.saveProfile(changed); reporter.popTask(); } catch (IOException e) { MainController.getInstance().view.textMessage(e .getMessage()); } } reporter.popTask(); reporter.popTask(); // pop root task } }.start(); } /** * Determines how much time the precalculations that will be deleted in * {@link #saveAllChanges()} took, in milliseconds. * * @return The deletion time, in milliseconds. */ public int getDeletionTime() { final ProfilesDiff diff = diff(); final Set<Profile> changedProfiles = diff.getChangedProfiles(); final Set<Profile> deletedProfiles = diff.getDeletedProfiles(); final Set<String> changedProfileNames = new HashSet<>(); for (Profile p : changedProfiles) { changedProfileNames.add(p.getName()); } int time = 0; for (final ProfileMapCombination pmc : ProfileMapManager.getInstance() .getPrecalculations()) { final Profile p = pmc.getProfile(); if (changedProfileNames.contains(p.getName()) || deletedProfiles.contains(p)) { time += pmc.getCalculationTime(); } } return time; } /** * Returns the profile that the user selected. Note that this is different * from the <i>current</i> profile (which is the one that the user has * currently selected, while the view is still visible); the selected * profile 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 profile that the user selected. */ public Profile getSelectedProfile() { return selectedProfile; } /** * Returns the precalculations that were deleted because a profile was * changed or deleted. * * @return The deleted precalculations. */ public Set<ProfileMapCombination> getDeletedPrecalculations() { final ProfilesDiff diff = diff(); final Set<Profile> profiles = diff.getDeletedProfiles(); profiles.addAll(diff.getChangedProfiles()); final Set<ProfileMapCombination> ret = new HashSet<>(); for (ProfileMapCombination combination : combinationsAtStartup) { if (profiles.contains(combination.getProfile())) { ret.add(combination); } } return ret; } public ProfilesDiff diff() { if (diff != null) { return diff; } return ProfilesDiff.calc(ProfileManager.getInstance().getProfiles(), new HashSet<>(profiles.values())); } public static class ProfilesDiff { /** * {@link Profile Profiles} that need to be * {@link ProfileManager#deleteProfile(Profile) deleted}. */ private final Set<Profile> deletedProfiles; /** * {@link Profile Profiles} that need to be * {@link ProfileManager#saveProfile(Profile) saved}, i. e. changed and * new profiles. */ private final Set<Profile> changedProfiles; private ProfilesDiff(final Set<Profile> deletedProfiles, final Set<Profile> changedProfiles) { this.deletedProfiles = deletedProfiles; this.changedProfiles = changedProfiles; } public Set<Profile> getDeletedProfiles() { return deletedProfiles; } public Set<Profile> getChangedProfiles() { return changedProfiles; } private static ProfilesDiff calc(final Set<Profile> from, final Set<Profile> to) { final Set<Profile> deletedProfiles = new HashSet<>(); final Set<Profile> changedProfiles = new HashSet<>(); for (final Profile p : from) { final String pName = p.getName(); boolean deleted = true; for (final Profile q : to) { if (pName.equals(q.getName())) { deleted = false; break; } } if (deleted) { deletedProfiles.add(p); } } for (final Profile q : to) { final String qName = q.getName(); boolean changed = true; for (final Profile p : from) { if (qName.equals(p.getName())) { changed = !q.equals(p); break; } } if (changed) { changedProfiles.add(q); } } return new ProfilesDiff(deletedProfiles, changedProfiles); } } }