001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.Collections; 009import java.util.Iterator; 010import java.util.List; 011import java.util.Map; 012import java.util.Map.Entry; 013 014import javax.swing.JOptionPane; 015 016import org.openstreetmap.josm.gui.MainApplication; 017import org.openstreetmap.josm.spi.preferences.IPreferences; 018import org.openstreetmap.josm.spi.preferences.ListListSetting; 019import org.openstreetmap.josm.spi.preferences.ListSetting; 020import org.openstreetmap.josm.spi.preferences.MapListSetting; 021import org.openstreetmap.josm.spi.preferences.Setting; 022import org.openstreetmap.josm.spi.preferences.StringSetting; 023import org.openstreetmap.josm.tools.Logging; 024import org.openstreetmap.josm.tools.Utils; 025 026/** 027 * Helper class to do specific Preferences operation - appending, replacing, deletion by key and by value 028 * @since 12634 (extracted from {@code CustomConfigurator}) 029 */ 030public final class PreferencesUtils { 031 032 private static volatile StringBuilder summary = new StringBuilder(); 033 034 private PreferencesUtils() { 035 // Hide implicit public constructor for utility class 036 } 037 038 /** 039 * Log a formatted message. 040 * @param fmt format 041 * @param vars arguments 042 * @see String#format 043 * @since 12826 044 */ 045 public static void log(String fmt, Object... vars) { 046 summary.append(String.format(fmt, vars)); 047 } 048 049 /** 050 * Log a message. 051 * @param s message to log 052 * @since 12826 053 */ 054 public static void log(String s) { 055 summary.append(s).append('\n'); 056 } 057 058 /** 059 * Log an exception. 060 * @param e exception to log 061 * @param s message prefix 062 * @since 12826 063 */ 064 public static void log(Exception e, String s) { 065 summary.append(s).append(' ').append(Logging.getErrorMessage(e)).append('\n'); 066 } 067 068 /** 069 * Returns the log. 070 * @return the log 071 * @since 12826 072 */ 073 public static String getLog() { 074 return summary.toString(); 075 } 076 077 /** 078 * Resets the log. 079 * @since 12826 080 */ 081 public static void resetLog() { 082 summary = new StringBuilder(); 083 } 084 085 public static void replacePreferences(Preferences fragment, Preferences mainpref) { 086 for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) { 087 mainpref.putSetting(entry.getKey(), entry.getValue()); 088 } 089 } 090 091 public static void appendPreferences(Preferences fragment, Preferences mainpref) { 092 for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) { 093 String key = entry.getKey(); 094 if (entry.getValue() instanceof StringSetting) { 095 mainpref.putSetting(key, entry.getValue()); 096 } else if (entry.getValue() instanceof ListSetting) { 097 ListSetting lSetting = (ListSetting) entry.getValue(); 098 List<String> newItems = getList(mainpref, key, true); 099 if (newItems == null) continue; 100 for (String item : lSetting.getValue()) { 101 // add nonexisting elements to then list 102 if (!newItems.contains(item)) { 103 newItems.add(item); 104 } 105 } 106 mainpref.putList(key, newItems); 107 } else if (entry.getValue() instanceof ListListSetting) { 108 ListListSetting llSetting = (ListListSetting) entry.getValue(); 109 List<List<String>> newLists = getListOfLists(mainpref, key, true); 110 if (newLists == null) continue; 111 112 for (List<String> list : llSetting.getValue()) { 113 // add nonexisting list (equals comparison for lists is used implicitly) 114 if (!newLists.contains(list)) { 115 newLists.add(list); 116 } 117 } 118 mainpref.putListOfLists(key, newLists); 119 } else if (entry.getValue() instanceof MapListSetting) { 120 MapListSetting mlSetting = (MapListSetting) entry.getValue(); 121 List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true); 122 if (newMaps == null) continue; 123 124 // get existing properties as list of maps 125 126 for (Map<String, String> map : mlSetting.getValue()) { 127 // add nonexisting map (equals comparison for maps is used implicitly) 128 if (!newMaps.contains(map)) { 129 newMaps.add(map); 130 } 131 } 132 mainpref.putListOfMaps(entry.getKey(), newMaps); 133 } 134 } 135 } 136 137 /** 138 * Delete items from {@code mainpref} collections that match items from {@code fragment} collections. 139 * @param fragment preferences 140 * @param mainpref main preferences 141 */ 142 public static void deletePreferenceValues(Preferences fragment, Preferences mainpref) { 143 144 for (Entry<String, Setting<?>> entry : fragment.settingsMap.entrySet()) { 145 String key = entry.getKey(); 146 if (entry.getValue() instanceof StringSetting) { 147 StringSetting sSetting = (StringSetting) entry.getValue(); 148 // if mentioned value found, delete it 149 if (sSetting.equals(mainpref.settingsMap.get(key))) { 150 mainpref.put(key, null); 151 } 152 } else if (entry.getValue() instanceof ListSetting) { 153 ListSetting lSetting = (ListSetting) entry.getValue(); 154 List<String> newItems = getList(mainpref, key, true); 155 if (newItems == null) continue; 156 157 // remove mentioned items from collection 158 for (String item : lSetting.getValue()) { 159 log("Deleting preferences: from list %s: %s\n", key, item); 160 newItems.remove(item); 161 } 162 mainpref.putList(entry.getKey(), newItems); 163 } else if (entry.getValue() instanceof ListListSetting) { 164 ListListSetting llSetting = (ListListSetting) entry.getValue(); 165 List<List<String>> newLists = getListOfLists(mainpref, key, true); 166 if (newLists == null) continue; 167 168 // if items are found in one of lists, remove that list! 169 Iterator<List<String>> listIterator = newLists.iterator(); 170 while (listIterator.hasNext()) { 171 Collection<String> list = listIterator.next(); 172 for (Collection<String> removeList : llSetting.getValue()) { 173 if (list.containsAll(removeList)) { 174 // remove current list, because it matches search criteria 175 log("Deleting preferences: list from lists %s: %s\n", key, list); 176 listIterator.remove(); 177 } 178 } 179 } 180 181 mainpref.putListOfLists(key, newLists); 182 } else if (entry.getValue() instanceof MapListSetting) { 183 MapListSetting mlSetting = (MapListSetting) entry.getValue(); 184 List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true); 185 if (newMaps == null) continue; 186 187 Iterator<Map<String, String>> mapIterator = newMaps.iterator(); 188 while (mapIterator.hasNext()) { 189 Map<String, String> map = mapIterator.next(); 190 for (Map<String, String> removeMap : mlSetting.getValue()) { 191 if (map.entrySet().containsAll(removeMap.entrySet())) { 192 // the map contain all mentioned key-value pair, so it should be deleted from "maps" 193 log("Deleting preferences: deleting map from maps %s: %s\n", key, map); 194 mapIterator.remove(); 195 } 196 } 197 } 198 mainpref.putListOfMaps(entry.getKey(), newMaps); 199 } 200 } 201 } 202 203 public static void deletePreferenceKeyByPattern(String pattern, Preferences pref) { 204 Map<String, Setting<?>> allSettings = pref.getAllSettings(); 205 for (Entry<String, Setting<?>> entry : allSettings.entrySet()) { 206 String key = entry.getKey(); 207 if (key.matches(pattern)) { 208 log("Deleting preferences: deleting key from preferences: " + key); 209 pref.putSetting(key, null); 210 } 211 } 212 } 213 214 public static void deletePreferenceKey(String key, Preferences pref) { 215 Map<String, Setting<?>> allSettings = pref.getAllSettings(); 216 if (allSettings.containsKey(key)) { 217 log("Deleting preferences: deleting key from preferences: " + key); 218 pref.putSetting(key, null); 219 } 220 } 221 222 private static List<String> getList(Preferences mainpref, String key, boolean warnUnknownDefault) { 223 ListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListSetting.class); 224 ListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListSetting.class); 225 if (existing == null && defaults == null) { 226 if (warnUnknownDefault) defaultUnknownWarning(key); 227 return null; 228 } 229 if (existing != null) 230 return new ArrayList<>(existing.getValue()); 231 else 232 return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue()); 233 } 234 235 private static List<List<String>> getListOfLists(Preferences mainpref, String key, boolean warnUnknownDefault) { 236 ListListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListListSetting.class); 237 ListListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListListSetting.class); 238 239 if (existing == null && defaults == null) { 240 if (warnUnknownDefault) defaultUnknownWarning(key); 241 return null; 242 } 243 if (existing != null) 244 return new ArrayList<>(existing.getValue()); 245 else 246 return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue()); 247 } 248 249 private static List<Map<String, String>> getListOfStructs(Preferences mainpref, String key, boolean warnUnknownDefault) { 250 MapListSetting existing = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class); 251 MapListSetting defaults = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class); 252 253 if (existing == null && defaults == null) { 254 if (warnUnknownDefault) defaultUnknownWarning(key); 255 return null; 256 } 257 258 if (existing != null) 259 return new ArrayList<>(existing.getValue()); 260 else 261 return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue()); 262 } 263 264 private static void defaultUnknownWarning(String key) { 265 log("Warning: Unknown default value of %s , skipped\n", key); 266 JOptionPane.showMessageDialog( 267 MainApplication.getMainFrame(), 268 tr("<html>Settings file asks to append preferences to <b>{0}</b>,<br/> "+ 269 "but its default value is unknown at this moment.<br/> " + 270 "Please activate corresponding function manually and retry importing.", key), 271 tr("Warning"), 272 JOptionPane.WARNING_MESSAGE); 273 } 274 275 public static void showPrefs(Preferences tmpPref) { 276 Logging.info("properties: " + tmpPref.settingsMap); 277 } 278 279 /** 280 * Gets an boolean that may be specialized 281 * @param prefs the preferences 282 * @param key The basic key 283 * @param specName The sub-key to append to the key 284 * @param def The default value 285 * @return The boolean value or the default value if it could not be parsed 286 * @since 12891 287 */ 288 public static boolean getBoolean(IPreferences prefs, final String key, final String specName, final boolean def) { 289 synchronized (prefs) { 290 boolean generic = prefs.getBoolean(key, def); 291 String skey = key+'.'+specName; 292 String svalue = prefs.get(skey, null); 293 if (svalue != null) 294 return Boolean.parseBoolean(svalue); 295 else 296 return generic; 297 } 298 } 299 300 /** 301 * Gets an integer that may be specialized 302 * @param prefs the preferences 303 * @param key The basic key 304 * @param specName The sub-key to append to the key 305 * @param def The default value 306 * @return The integer value or the default value if it could not be parsed 307 * @since 12891 308 */ 309 public static int getInteger(IPreferences prefs, String key, String specName, int def) { 310 synchronized (prefs) { 311 String v = prefs.get(key+'.'+specName); 312 if (v.isEmpty()) 313 v = prefs.get(key, Integer.toString(def)); 314 if (v.isEmpty()) 315 return def; 316 317 try { 318 return Integer.parseInt(v); 319 } catch (NumberFormatException e) { 320 // fall out 321 Logging.trace(e); 322 } 323 return def; 324 } 325 } 326 327 /** 328 * Removes a value from a given String list 329 * @param prefs the preferences 330 * @param key The preference key the list is stored with 331 * @param value The value that should be removed in the list 332 * @since 12894 333 */ 334 public static void removeFromList(IPreferences prefs, String key, String value) { 335 synchronized (prefs) { 336 List<String> a = new ArrayList<>(prefs.getList(key, Collections.<String>emptyList())); 337 a.remove(value); 338 prefs.putList(key, a); 339 } 340 } 341 342 /** 343 * Saves at most {@code maxsize} items of list {@code val}. 344 * @param prefs the preferences 345 * @param key key 346 * @param maxsize max number of items to save 347 * @param val value 348 * @return {@code true}, if something has changed (i.e. value is different than before) 349 * @since 12894 350 */ 351 public static boolean putListBounded(IPreferences prefs, String key, int maxsize, List<String> val) { 352 List<String> newCollection = new ArrayList<>(Math.min(maxsize, val.size())); 353 for (String i : val) { 354 if (newCollection.size() >= maxsize) { 355 break; 356 } 357 newCollection.add(i); 358 } 359 return prefs.putList(key, newCollection); 360 } 361 362}