// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.graphview.plugin.preferences; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.AXLELOAD; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.HEIGHT; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.LENGTH; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.MAX_INCLINE_DOWN; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.MAX_INCLINE_UP; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.MAX_TRACKTYPE; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.SPEED; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.SURFACE_BLACKLIST; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.WEIGHT; import static org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyTypes.WIDTH; import static org.openstreetmap.josm.tools.I18n.marktr; import java.awt.Color; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Observable; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.plugins.graphview.core.access.AccessParameters; import org.openstreetmap.josm.plugins.graphview.core.access.AccessType; import org.openstreetmap.josm.plugins.graphview.core.property.VehiclePropertyType; import org.openstreetmap.josm.plugins.graphview.core.visualisation.ColorScheme; import org.openstreetmap.josm.plugins.graphview.plugin.layer.PreferencesColorScheme; import org.openstreetmap.josm.plugins.graphview.plugin.preferences.VehiclePropertyStringParser.PropertyValueSyntaxException; /** * preferences of the GraphView plugin. * Observers will be notified when preferences change, * changes will also be synchronized (two-way) with JOSM's preference storage. * This is a singleton class. * * Note: Currently, manual updates in the "advanced preferences" will not have any effect * because this class isn't registered as a preference listener. */ public final class GraphViewPreferences extends Observable { private static GraphViewPreferences instance; /** * returns the single instance of GraphViewPreferences. */ public static GraphViewPreferences getInstance() { if (instance == null) { instance = new GraphViewPreferences(); } return instance; } private boolean useInternalRulesets; private File rulesetFolder; private File currentRulesetFile; private InternalRuleset currentInternalRuleset; private String currentParameterBookmarkName; private Map<String, PreferenceAccessParameters> parameterBookmarks; private ColorScheme currentColorScheme; private Color nodeColor; private Color segmentColor; private Color arrowheadFillColor; private boolean separateDirections; private double arrowheadPlacement; public synchronized boolean getUseInternalRulesets() { return useInternalRulesets; } public synchronized void setUseInternalRulesets(boolean useInternalRulesets) { this.useInternalRulesets = useInternalRulesets; } public synchronized File getRulesetFolder() { return rulesetFolder; } public synchronized void setRulesetFolder(File rulesetFolder) { this.rulesetFolder = rulesetFolder; } public synchronized File getCurrentRulesetFile() { return currentRulesetFile; } public synchronized void setCurrentRulesetFile(File currentRulesetFile) { this.currentRulesetFile = currentRulesetFile; } public synchronized InternalRuleset getCurrentInternalRuleset() { return currentInternalRuleset; } public synchronized void setCurrentInternalRuleset(InternalRuleset internalRuleset) { this.currentInternalRuleset = internalRuleset; } /** * returns the name (map key) of the currently selected parameter bookmark * or null if none is selected. * If a name is returned, is has to be a key of the map returned by * {@link #getParameterBookmarks()}. */ public synchronized String getCurrentParameterBookmarkName() { assert parameterBookmarks.containsKey(currentParameterBookmarkName); return currentParameterBookmarkName; } /** * returns the access parameters of the currently selected parameter bookmark * or null if none is selected. */ public synchronized AccessParameters getCurrentParameterBookmark() { if (currentParameterBookmarkName == null) { return null; } else { assert parameterBookmarks.containsKey(currentParameterBookmarkName); return parameterBookmarks.get(currentParameterBookmarkName); } } /** * sets the active parameter bookmark using its name as an identifier * @param currentParameters name of bookmark to set or null (no active bookmark). * Non-null values must be keys of the map returned by * {@link #getParameterBookmarks()}. */ public synchronized void setCurrentParameterBookmarkName(String parameterBookmarkName) { assert parameterBookmarks.containsKey(parameterBookmarkName); this.currentParameterBookmarkName = parameterBookmarkName; } public synchronized Map<String, PreferenceAccessParameters> getParameterBookmarks() { return Collections.unmodifiableMap(parameterBookmarks); } public synchronized void setParameterBookmarks( Map<String, PreferenceAccessParameters> parameterBookmarks) { assert parameterBookmarks != null; this.parameterBookmarks = new HashMap<>(parameterBookmarks); } public synchronized ColorScheme getCurrentColorScheme() { return currentColorScheme; } public synchronized void setCurrentColorScheme(ColorScheme currentColorScheme) { this.currentColorScheme = currentColorScheme; } public synchronized Color getNodeColor() { return nodeColor; } public synchronized void setNodeColor(Color nodeColor) { this.nodeColor = nodeColor; } public synchronized Color getSegmentColor() { return segmentColor; } public synchronized void setSegmentColor(Color segmentColor) { this.segmentColor = segmentColor; } public synchronized Color getArrowheadFillColor() { return arrowheadFillColor; } public synchronized void setArrowheadFillColor(Color arrowheadFillColor) { this.arrowheadFillColor = arrowheadFillColor; } public synchronized boolean getSeparateDirections() { return separateDirections; } public synchronized void setSeparateDirections(boolean separateDirections) { this.separateDirections = separateDirections; } public synchronized double getArrowheadPlacement() { return arrowheadPlacement; } public synchronized void setArrowheadPlacement(double arrowheadPlacement) { this.arrowheadPlacement = arrowheadPlacement; } /** * writes changes to JOSM's preferences and notifies observers. * Must be called explicitly after setters (to prevent distributing incomplete changes). */ public void distributeChanges() { writePreferences(); setChanged(); notifyObservers(); } private GraphViewPreferences() { /* set defaults first (in case preferences are incomplete) */ fillDefaults(); /* read preferences and overwrite defaults */ readPreferences(); /* write preferences * (this will restore missing/defect preferences, * but will simply rewrite valid preferences) */ writePreferences(); } private void fillDefaults() { parameterBookmarks = GraphViewPreferenceDefaults.createDefaultAccessParameterBookmarks(); if (parameterBookmarks.size() > 0) { currentParameterBookmarkName = parameterBookmarks.keySet().iterator().next(); } else { currentParameterBookmarkName = null; } useInternalRulesets = true; rulesetFolder = GraphViewPreferenceDefaults.getDefaultRulesetFolder(); currentRulesetFile = null; currentInternalRuleset = null; currentColorScheme = new PreferencesColorScheme(this); separateDirections = false; } private void writePreferences() { Main.pref.put("graphview.parameterBookmarks", createAccessParameterBookmarksString(parameterBookmarks)); if (currentParameterBookmarkName != null) { Main.pref.put("graphview.activeBookmark", currentParameterBookmarkName); } Main.pref.put("graphview.useInternalRulesets", useInternalRulesets); Main.pref.put("graphview.rulesetFolder", rulesetFolder.getPath()); if (currentRulesetFile != null) { Main.pref.put("graphview.rulesetFile", currentRulesetFile.getPath()); } if (currentInternalRuleset != null) { Main.pref.put("graphview.rulesetResource", currentInternalRuleset.toString()); } Main.pref.putColor(marktr("graphview default node"), Color.WHITE); Main.pref.putColor(marktr("graphview default segment"), Color.WHITE); Main.pref.putColor(marktr("graphview arrowhead core"), Color.BLACK); Main.pref.put("graphview.separateDirections", separateDirections); Main.pref.putDouble("graphview.arrowheadPlacement", arrowheadPlacement); } private void readPreferences() { if (!Main.pref.get("graphview.parameterBookmarks").isEmpty()) { String bookmarksString = Main.pref.get("graphview.parameterBookmarks"); parameterBookmarks = parseAccessParameterBookmarksString(bookmarksString); } if (!Main.pref.get("graphview.activeBookmark").isEmpty()) { currentParameterBookmarkName = Main.pref.get("graphview.activeBookmark"); } if (!parameterBookmarks.containsKey(currentParameterBookmarkName)) { currentParameterBookmarkName = null; } useInternalRulesets = Main.pref.getBoolean("graphview.useInternalRulesets", true); if (!Main.pref.get("graphview.rulesetFolder").isEmpty()) { String dirString = Main.pref.get("graphview.rulesetFolder"); rulesetFolder = new File(dirString); } if (!Main.pref.get("graphview.rulesetFile").isEmpty()) { String fileString = Main.pref.get("graphview.rulesetFile"); currentRulesetFile = new File(fileString); } if (!Main.pref.get("graphview.rulesetResource").isEmpty()) { String rulesetString = Main.pref.get("graphview.rulesetResource"); //get the enum value for the string //(InternalRuleset.valueOf cannot be used because it cannot handle invalid strings well) for (InternalRuleset ruleset : InternalRuleset.values()) { if (ruleset.toString().equals(rulesetString)) { currentInternalRuleset = ruleset; break; } } } nodeColor = Main.pref.getColor(marktr("graphview default node"), Color.WHITE); segmentColor = Main.pref.getColor(marktr("graphview default segment"), Color.WHITE); arrowheadFillColor = Main.pref.getColor(marktr("graphview arrowhead core"), Color.BLACK); separateDirections = Main.pref.getBoolean("graphview.separateDirections", false); arrowheadPlacement = Main.pref.getDouble("graphview.arrowheadPlacement", 1.0); if (arrowheadPlacement < 0.0 || arrowheadPlacement >= 1.0) { arrowheadPlacement = 1.0; } } private static final Pattern ACCESS_PARAM_PATTERN = Pattern.compile("^([^;]*);([^;]*);types=\\{([^\\}]*)\\};properties=\\{([^\\}]*)\\}$"); private static final Pattern PROPERTY_MAP_ENTRY_PATTERN = Pattern.compile("^([^=]*)=(.*)$"); private static final Map<VehiclePropertyType<?>, String> VEHICLE_PROPERTY_TYPE_NAME_MAP = new HashMap<>(); static { VEHICLE_PROPERTY_TYPE_NAME_MAP.put(AXLELOAD, "AXLELOAD"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(HEIGHT, "HEIGHT"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(LENGTH, "LENGTH"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(MAX_INCLINE_DOWN, "MAX_INCLINE_DOWN"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(MAX_INCLINE_UP, "MAX_INCLINE_UP"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(MAX_TRACKTYPE, "MAX_TRACKTYPE"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(SPEED, "SPEED"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(SURFACE_BLACKLIST, "SURFACE_BLACKLIST"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(WEIGHT, "WEIGHT"); VEHICLE_PROPERTY_TYPE_NAME_MAP.put(WIDTH, "WIDTH"); } private static String createAccessParameterBookmarksString( Map<String, PreferenceAccessParameters> parameterBookmarks) { StringBuilder stringBuilder = new StringBuilder(); boolean firstEntry = true; for (String bookmarkName : parameterBookmarks.keySet()) { if (!firstEntry) { stringBuilder.append("|"); } else { firstEntry = false; } stringBuilder.append(createAccessParameterBookmarkString( bookmarkName, parameterBookmarks.get(bookmarkName))); } return stringBuilder.toString(); } private static String createAccessParameterBookmarkString( String bookmarkName, PreferenceAccessParameters parameters) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(bookmarkName).append(";"); stringBuilder.append(parameters.getAccessClass()); stringBuilder.append(";types={"); for (AccessType accessType : AccessType.values()) { if (parameters.getAccessTypeUsable(accessType)) { stringBuilder.append(accessType).append(","); } } if (stringBuilder.charAt(stringBuilder.length()-1) == ',') { stringBuilder.deleteCharAt(stringBuilder.length()-1); } stringBuilder.append("}"); stringBuilder.append(";properties={"); for (VehiclePropertyType<?> vehiclePropertyType : VEHICLE_PROPERTY_TYPE_NAME_MAP.keySet()) { String propertyString = parameters.getVehiclePropertyString(vehiclePropertyType); if (propertyString != null) { stringBuilder.append(VEHICLE_PROPERTY_TYPE_NAME_MAP.get(vehiclePropertyType)); stringBuilder.append("="); stringBuilder.append(propertyString); stringBuilder.append(","); } } if (stringBuilder.charAt(stringBuilder.length()-1) == ',') { stringBuilder.deleteCharAt(stringBuilder.length()-1); } stringBuilder.append("}"); assert ACCESS_PARAM_PATTERN.matcher(stringBuilder.toString()).matches(); return stringBuilder.toString(); } private static Map<String, PreferenceAccessParameters> parseAccessParameterBookmarksString( String string) { Map<String, PreferenceAccessParameters> resultMap = new HashMap<>(); String[] bookmarkStrings = string.split("\\|"); for (String bookmarkString : bookmarkStrings) { parseAccessParameterBookmarkString(bookmarkString, resultMap); } return resultMap; } private static void parseAccessParameterBookmarkString(String bookmarkString, Map<String, PreferenceAccessParameters> resultMap) { Matcher matcher = ACCESS_PARAM_PATTERN.matcher(bookmarkString); if (matcher.matches()) { String bookmarkName = matcher.group(1); String accessClass = matcher.group(2); String[] accessTypeStrings = matcher.group(3).split(","); Collection<AccessType> accessTypes = new LinkedList<>(); for (String accessTypeString : accessTypeStrings) { AccessType accessType = AccessType.valueOf(accessTypeString); if (accessType != null) { accessTypes.add(accessType); } } String[] vehiclePropertyStrings = matcher.group(4).split(","); Map<VehiclePropertyType<?>, String> vehiclePropertyMap = new HashMap<>(); for (String vehiclePropertyString : vehiclePropertyStrings) { Matcher entryMatcher = PROPERTY_MAP_ENTRY_PATTERN.matcher(vehiclePropertyString); if (entryMatcher.matches()) { String propertyTypeString = entryMatcher.group(1); String propertyValueString = entryMatcher.group(2); for (VehiclePropertyType<?> propertyType : VEHICLE_PROPERTY_TYPE_NAME_MAP.keySet()) { if (propertyTypeString.equals( VEHICLE_PROPERTY_TYPE_NAME_MAP.get(propertyType))) { vehiclePropertyMap.put(propertyType, propertyValueString); } } } } try { PreferenceAccessParameters accessParameters = new PreferenceAccessParameters(accessClass, accessTypes, vehiclePropertyMap); resultMap.put(bookmarkName, accessParameters); } catch (PropertyValueSyntaxException e) { //don't add bookmark } } } }