package edu.kit.pse.ws2013.routekit.models; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import edu.kit.pse.ws2013.routekit.map.StreetMap; import edu.kit.pse.ws2013.routekit.profiles.Profile; /** * A combination of a {@link Profile} and a {@link StreetMap}, optionally with * results of a pre-calculation for them. */ public class ProfileMapCombination { private final StreetMap map; private final Profile profile; protected Weights weights; protected ArcFlags arcFlags; protected int calculationTime; /** * Creates a new {@code ProfileMapCombination} of the given street map and * profile without a precalculation. * * @param map * the {@link StreetMap} * @param profile * the {@link Profile} */ public ProfileMapCombination(StreetMap map, Profile profile) { this.map = map; this.profile = profile; } /** * Creates a new {@code ProfileMapCombination} of the given street map and * profile with a precalculation. * * @param map * the {@link StreetMap} * @param profile * the {@link Profile} * @param weights * the calculated {@link Weights} * @param arcFlags * the calculated {@link ArcFlags} * @param calculationTime * the time needed for the precalculation */ public ProfileMapCombination(StreetMap map, Profile profile, Weights weights, ArcFlags arcFlags, int calculationTime) { this(map, profile); this.weights = weights; this.arcFlags = arcFlags; this.calculationTime = calculationTime; } /** * Returns if there is a precalculation for this * {@link ProfileMapCombination}, that is, if there are {@link Weights} and * {@link ArcFlags}. * * @return {@code true} if there is a precalculation, {@code false} * otherwise. */ public boolean isCalculated() { return weights != null && arcFlags != null; } /** * Returns the {@link StreetMap} belonging to this combination. * * @return the street map */ public StreetMap getStreetMap() { return map; } /** * Returns the {@link Profile} belonging to this combination. * * @return the profile */ public Profile getProfile() { return profile; } /** * Returns the edge weights calculated for this * {@code ProfileMapCombination}. * * @return the {@link Weights} * @throws IllegalStateException * if the weights have not been calculated yet */ public Weights getWeights() { if (weights == null) { throw new IllegalStateException(); } return weights; } /** * Returns the {@link ArcFlags} calculated for this combination. * * @return the arc flags * @throws IllegalStateException * if there is no precalculation for this combination */ public ArcFlags getArcFlags() { if (!isCalculated()) { throw new IllegalStateException(); } return arcFlags; } /** * @deprecated Use {@link #setWeights(Weights, int)} instead. */ @Deprecated public void setWeights(Weights weights) { this.weights = weights; } /** * Sets the edge weights for this {@code ProfileMapCombination}. * * @param weights * the calculated {@link Weights} * @param calculationTime * the time needed for calculating the weights */ public void setWeights(Weights weights, int calculationTime) { this.weights = weights; assert (calculationTime > 0); this.calculationTime += calculationTime; } /** * @deprecated Use {@link #setArcFlags(ArcFlags, int)} instead. */ @Deprecated public void setArcFlags(ArcFlags arcFlags) { this.arcFlags = arcFlags; } /** * Sets the {@link ArcFlags} for this combination. * * @param arcFlags * the calculated {@link ArcFlags} * @param calculationTime * the time needed for calculating the flags */ public void setArcFlags(ArcFlags arcFlags, int calculationTime) { this.arcFlags = arcFlags; assert (calculationTime >= 0); this.calculationTime += calculationTime; } /** * The time that the precalculation of this {@link ProfileMapCombination} * took, in milliseconds. For not yet precalculated combinations, this is * {@code 0}. * * @return The calculation time of this combination, in milliseconds. */ public int getCalculationTime() { return calculationTime; } /** * Ensures that this {@link ProfileMapCombination} / precalculation is * completely loaded. This is a noop for normal combinations. * * @param reporter * A {@link ProgressReporter} to report loading progress to. * @see #loadLazily(Profile, StreetMap, File) */ public void ensureLoaded(ProgressReporter reporter) { map.ensureLoaded(reporter); } @Override public String toString() { return saveFileName(profile, map); } private static String saveFileName(Profile profile, StreetMap map) { return map.getName() + " + " + profile.getName(); } /** * Saves the {@link ProfileMapCombination} to the given directory by saving * the {@link #getWeights() Weights}, the {@link #getArcFlags() ArcFlags} * and the {@link #getCalculationTime() calculation time} to the files * {@code <name>.weights}, {@code <name>.arcflags} and * {@code <name>.time} (where {@code <name>} is the name of the * map " + " the name of the profile). * * @param directory * The directory to which the {@link ProfileMapCombination} * should be saved. * @throws IOException * If the files can’t be written. * @see #load(File) */ public void save(File directory) throws IOException { if (!directory.exists()) { directory.mkdir(); } if (!directory.isDirectory()) { throw new IllegalArgumentException(directory.toString() + " is not a directory!"); } String name = saveFileName(profile, map); File weightsFile = new File(directory, name + ".weights"); File arcFlagsFile = new File(directory, name + ".arcflags"); File timeFile = new File(directory, name + ".time"); getWeights().save(weightsFile); getArcFlags().save(arcFlagsFile); try (BufferedWriter bw = new BufferedWriter(new FileWriter(timeFile))) { bw.write(Integer.toString(calculationTime) + "\n"); } } /** * Loads a {@link ProfileMapCombination} from the given directory by loading * the {@link #getWeights() Weights}, the {@link #getArcFlags() ArcFlags} * and the {@link #getCalculationTime() calculation time} from the files * {@code <name>.weights}, {@code <name>.arcflags} and * {@code <name>.time} (where {@code <name>} is the name of the * map " + " the name of the profile). * * @param profile * The profile. * @param map * The map. * @param directory * The directory from which the {@link ProfileMapCombination} * should be loaded. * @return A {@link ProfileMapCombination} containing the {@link Weights} * and {@link ArcFlags} from the given directory. * @throws IOException * If the files can’t be read. */ public static ProfileMapCombination load(Profile profile, StreetMap map, File directory) throws IOException { if (!directory.isDirectory()) { throw new IllegalArgumentException(directory.toString() + " is not a directory!"); } String name = saveFileName(profile, map); File weightsFile = new File(directory, name + ".weights"); File arcFlagsFile = new File(directory, name + ".arcflags"); File timeFile = new File(directory, name + ".time"); Weights weights = Weights.load(weightsFile); ArcFlags arcFlags = ArcFlags.load(arcFlagsFile); final int time; try (BufferedReader br = new BufferedReader(new FileReader(timeFile))) { time = Integer.parseInt(br.readLine()); } return new ProfileMapCombination(map, profile, weights, arcFlags, time); } /** * Loads a precalculation from the given directory in the same way as * {@link #load(File)}, except that the {@link Weights} and {@link Arcflags} * are loaded lazily – only on the first access. * * @param directory * The directory from which the {@link ProfileMapCombination} * should be loaded. * @return A kind of {@link ProfileMapCombination} that loads the * {@link Weights} and the {@link ArcFlags} lazily. * @throws IOException * If the calculation time file (which is read eagerly) can’t be * read. */ public static ProfileMapCombination loadLazily(Profile profile, StreetMap map, File directory) throws IOException { String name = saveFileName(profile, map); File weightsFile = new File(directory, name + ".weights"); File arcFlagsFile = new File(directory, name + ".arcflags"); File timeFile = new File(directory, name + ".time"); final int time; try (BufferedReader br = new BufferedReader(new FileReader(timeFile))) { time = Integer.parseInt(br.readLine()); } return new LazyProfileMapCombination(profile, map, weightsFile, arcFlagsFile, time); } private static class LazyProfileMapCombination extends ProfileMapCombination { private final File weightsFile; private final File arcFlagsFile; public LazyProfileMapCombination(Profile profile, StreetMap map, File weightsFile, File arcFlagsFile, int calculationTime) { super(map, profile); this.weightsFile = weightsFile; this.arcFlagsFile = arcFlagsFile; this.calculationTime = calculationTime; } @Override public boolean isCalculated() { // un-lazy getWeights(); getArcFlags(); return super.isCalculated(); } @Override public Weights getWeights() { if (this.weights == null) { try { this.weights = Weights.load(weightsFile); } catch (IOException e) { e.printStackTrace(); } } return weights; } @Override public ArcFlags getArcFlags() { if (this.arcFlags == null) { try { this.arcFlags = ArcFlags.load(arcFlagsFile); } catch (IOException e) { e.printStackTrace(); } } return arcFlags; } @Override public void ensureLoaded(ProgressReporter reporter) { reporter.setSubTasks(new float[] { .9f, .05f, .05f }); reporter.pushTask("Lade Karte"); super.ensureLoaded(reporter); reporter.nextTask("Lade Kantengewichte"); getWeights(); reporter.nextTask("Lade Arc-Flags"); getArcFlags(); reporter.popTask(); } } @Override public boolean equals(Object obj) { if (obj instanceof ProfileMapCombination) { ProfileMapCombination other = (ProfileMapCombination) obj; return profile.equals(other.profile) && map.equals(other.map); } return false; } @Override public int hashCode() { return calculationTime ^ profile.hashCode() ^ map.hashCode(); } }