source: josm/trunk/src/org/openstreetmap/josm/data/Preferences.java@ 14153

Last change on this file since 14153 was 14153, checked in by Don-vip, 6 years ago

see #15229 - deprecate Main.parent and Main itself

  • Property svn:eol-style set to native
File size: 30.1 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[74]2package org.openstreetmap.josm.data;
3
[9296]4import static org.openstreetmap.josm.tools.I18n.marktr;
[1326]5import static org.openstreetmap.josm.tools.I18n.tr;
[13647]6import static org.openstreetmap.josm.tools.Utils.getSystemEnv;
7import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
[1326]8
[13769]9import java.awt.GraphicsEnvironment;
[172]10import java.io.File;
[74]11import java.io.IOException;
12import java.io.PrintWriter;
[3938]13import java.io.Reader;
[8344]14import java.io.StringWriter;
[7082]15import java.nio.charset.StandardCharsets;
[13838]16import java.nio.file.InvalidPathException;
[74]17import java.util.ArrayList;
[172]18import java.util.Collection;
[1121]19import java.util.Collections;
[8344]20import java.util.HashMap;
[7834]21import java.util.HashSet;
[4612]22import java.util.Iterator;
[77]23import java.util.Map;
[4200]24import java.util.Map.Entry;
[11436]25import java.util.Optional;
[7834]26import java.util.Set;
[74]27import java.util.SortedMap;
28import java.util.TreeMap;
[11288]29import java.util.concurrent.TimeUnit;
[10657]30import java.util.function.Predicate;
[10824]31import java.util.stream.Stream;
[74]32
[1326]33import javax.swing.JOptionPane;
[4612]34import javax.xml.stream.XMLStreamException;
[1326]35
[13076]36import org.openstreetmap.josm.data.preferences.ColorInfo;
[14149]37import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
[12987]38import org.openstreetmap.josm.data.preferences.NamedColorProperty;
39import org.openstreetmap.josm.data.preferences.PreferencesReader;
40import org.openstreetmap.josm.data.preferences.PreferencesWriter;
[14153]41import org.openstreetmap.josm.gui.MainApplication;
[13076]42import org.openstreetmap.josm.io.OfflineAccessException;
43import org.openstreetmap.josm.io.OnlineResource;
[12987]44import org.openstreetmap.josm.spi.preferences.AbstractPreferences;
[13076]45import org.openstreetmap.josm.spi.preferences.Config;
[12987]46import org.openstreetmap.josm.spi.preferences.IBaseDirectories;
[12881]47import org.openstreetmap.josm.spi.preferences.ListSetting;
48import org.openstreetmap.josm.spi.preferences.Setting;
[6578]49import org.openstreetmap.josm.tools.CheckParameterUtil;
[10824]50import org.openstreetmap.josm.tools.ListenerList;
[12620]51import org.openstreetmap.josm.tools.Logging;
[14138]52import org.openstreetmap.josm.tools.PlatformManager;
[3848]53import org.openstreetmap.josm.tools.Utils;
[7315]54import org.xml.sax.SAXException;
[74]55
56/**
57 * This class holds all preferences for JOSM.
[807]58 *
[74]59 * Other classes can register their beloved properties here. All properties will be
60 * saved upon set-access.
[807]61 *
[4635]62 * Each property is a key=setting pair, where key is a String and setting can be one of
63 * 4 types:
64 * string, list, list of lists and list of maps.
[3708]65 * In addition, each key has a unique default value that is set when the value is first
66 * accessed using one of the get...() methods. You can use the same preference
67 * key in different parts of the code, but the default value must be the same
[4634]68 * everywhere. A default value of null means, the setting has been requested, but
69 * no default value was set. This is used in advanced preferences to present a list
70 * off all possible settings.
[3708]71 *
[4635]72 * At the moment, you cannot put the empty string for string properties.
73 * put(key, "") means, the property is removed.
[3708]74 *
[74]75 * @author imi
[7536]76 * @since 74
[74]77 */
[13021]78public class Preferences extends AbstractPreferences {
[8846]79
80 private static final String[] OBSOLETE_PREF_KEYS = {
81 };
82
[11288]83 private static final long MAX_AGE_DEFAULT_PREFERENCES = TimeUnit.DAYS.toSeconds(50);
[9821]84
[13021]85 private final IBaseDirectories dirs;
[7085]86
[4813]87 /**
[7085]88 * Determines if preferences file is saved each time a property is changed.
89 */
90 private boolean saveOnPut = true;
91
92 /**
[7027]93 * Maps the setting name to the current value of the setting.
[6578]94 * The map must not contain null as key or value. The mapped setting objects
95 * must not have a null value.
[3708]96 */
[7027]97 protected final SortedMap<String, Setting<?>> settingsMap = new TreeMap<>();
98
[6578]99 /**
[7027]100 * Maps the setting name to the default value of the setting.
[6578]101 * The map must not contain null as key or value. The value of the mapped
102 * setting objects can be null.
103 */
[7027]104 protected final SortedMap<String, Setting<?>> defaultsMap = new TreeMap<>();
105
[10608]106 private final Predicate<Entry<String, Setting<?>>> NO_DEFAULT_SETTINGS_ENTRY =
107 e -> !e.getValue().equals(defaultsMap.get(e.getKey()));
[9821]108
[7027]109 /**
[9311]110 * Indicates whether {@link #init(boolean)} completed successfully.
111 * Used to decide whether to write backup preference file in {@link #save()}
112 */
[9978]113 protected boolean initSuccessful;
[9311]114
[12881]115 private final ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listeners = ListenerList.create();
[116]116
[12881]117 private final HashMap<String, ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener>> keyListeners = new HashMap<>();
[10824]118
[14149]119 private static final Preferences defaultInstance = new Preferences(JosmBaseDirectories.getInstance());
120
[12885]121 /**
[12634]122 * Constructs a new {@code Preferences}.
123 */
124 public Preferences() {
[13021]125 this.dirs = Config.getDirs();
[12634]126 }
127
128 /**
[13021]129 * Constructs a new {@code Preferences}.
130 *
131 * @param dirs the directories to use for saving the preferences
132 */
133 public Preferences(IBaseDirectories dirs) {
134 this.dirs = dirs;
135 }
136
137 /**
[12634]138 * Constructs a new {@code Preferences} from an existing instance.
139 * @param pref existing preferences to copy
140 * @since 12634
141 */
142 public Preferences(Preferences pref) {
[13021]143 this(pref.dirs);
[12634]144 settingsMap.putAll(pref.settingsMap);
145 defaultsMap.putAll(pref.defaultsMap);
146 }
147
148 /**
[14149]149 * Returns the main (default) preferences instance.
150 * @return the main (default) preferences instance
151 * @since 14149
152 */
153 public static Preferences main() {
154 return defaultInstance;
155 }
156
157 /**
[7027]158 * Adds a new preferences listener.
159 * @param listener The listener to add
[12882]160 * @since 12881
[7027]161 */
[12840]162 @Override
[12881]163 public void addPreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
[2655]164 if (listener != null) {
[10824]165 listeners.addListener(listener);
[2618]166 }
167 }
168
[7027]169 /**
170 * Removes a preferences listener.
171 * @param listener The listener to remove
[12882]172 * @since 12881
[7027]173 */
[12840]174 @Override
[12881]175 public void removePreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
[10824]176 listeners.removeListener(listener);
[2618]177 }
178
[10824]179 /**
180 * Adds a listener that only listens to changes in one preference
181 * @param key The preference key to listen to
182 * @param listener The listener to add.
[12882]183 * @since 12881
[10824]184 */
[12840]185 @Override
[12881]186 public void addKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
[10824]187 listenersForKey(key).addListener(listener);
188 }
189
190 /**
191 * Adds a weak listener that only listens to changes in one preference
192 * @param key The preference key to listen to
193 * @param listener The listener to add.
194 * @since 10824
195 */
[12881]196 public void addWeakKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
[10824]197 listenersForKey(key).addWeakListener(listener);
198 }
199
[12881]200 private ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listenersForKey(String key) {
[12865]201 return keyListeners.computeIfAbsent(key, k -> ListenerList.create());
[10824]202 }
203
[12885]204 /**
[10824]205 * Removes a listener that only listens to changes in one preference
206 * @param key The preference key to listen to
207 * @param listener The listener to add.
[12882]208 * @since 12881
[10824]209 */
[12840]210 @Override
[12881]211 public void removeKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
[11553]212 Optional.ofNullable(keyListeners.get(key)).orElseThrow(
213 () -> new IllegalArgumentException("There are no listeners registered for " + key))
214 .removeListener(listener);
[10824]215 }
216
[7027]217 protected void firePreferenceChanged(String key, Setting<?> oldValue, Setting<?> newValue) {
[12885]218 final org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent evt =
219 new org.openstreetmap.josm.spi.preferences.DefaultPreferenceChangeEvent(key, oldValue, newValue);
[10824]220 listeners.fireEvent(listener -> listener.preferenceChanged(evt));
221
[12881]222 ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> forKey = keyListeners.get(key);
[10824]223 if (forKey != null) {
224 forKey.fireEvent(listener -> listener.preferenceChanged(evt));
[2618]225 }
226 }
227
[12885]228 /**
[13173]229 * Get the base name of the JOSM directories for preferences, cache and user data.
[11162]230 * Default value is "JOSM", unless overridden by system property "josm.dir.name".
[13173]231 * @return the base name of the JOSM directories for preferences, cache and user data
[11162]232 */
[14148]233 public static String getJOSMDirectoryBaseName() {
[13647]234 String name = getSystemProperty("josm.dir.name");
[11162]235 if (name != null)
236 return name;
237 else
238 return "JOSM";
239 }
240
241 /**
[13021]242 * Get the base directories associated with this preference instance.
243 * @return the base directories
244 */
245 public IBaseDirectories getDirs() {
246 return dirs;
247 }
248
249 /**
[9821]250 * Returns the user preferences file (preferences.xml).
[7834]251 * @return The user preferences file (preferences.xml)
252 */
[2053]253 public File getPreferenceFile() {
[13023]254 return new File(dirs.getPreferencesDirectory(false), "preferences.xml");
[4592]255 }
256
[5874]257 /**
[9821]258 * Returns the cache file for default preferences.
259 * @return the cache file for default preferences
260 */
261 public File getDefaultsCacheFile() {
[13023]262 return new File(dirs.getCacheDirectory(true), "default_preferences.xml");
[9821]263 }
264
265 /**
266 * Returns the user plugin directory.
[5874]267 * @return The user plugin directory
268 */
[2817]269 public File getPluginsDirectory() {
[13023]270 return new File(dirs.getUserDataDirectory(false), "plugins");
[1169]271 }
[4937]272
[8870]273 private static void addPossibleResourceDir(Set<String> locations, String s) {
[7834]274 if (s != null) {
[1857]275 if (!s.endsWith(File.separator)) {
[7834]276 s += File.separator;
[1857]277 }
[1169]278 locations.add(s);
279 }
[7834]280 }
281
282 /**
283 * Returns a set of all existing directories where resources could be stored.
284 * @return A set of all existing directories where resources could be stored.
285 */
[14145]286 public static Collection<String> getAllPossiblePreferenceDirs() {
[7834]287 Set<String> locations = new HashSet<>();
[14149]288 addPossibleResourceDir(locations, defaultInstance.dirs.getPreferencesDirectory(false).getPath());
289 addPossibleResourceDir(locations, defaultInstance.dirs.getUserDataDirectory(false).getPath());
[13647]290 addPossibleResourceDir(locations, getSystemEnv("JOSM_RESOURCES"));
291 addPossibleResourceDir(locations, getSystemProperty("josm.resources"));
[14144]292 locations.addAll(PlatformManager.getPlatform().getPossiblePreferenceDirs());
[1169]293 return locations;
294 }
[807]295
[3708]296 /**
[12987]297 * Get all named colors, including customized and the default ones.
298 * @return a map of all named colors (maps preference key to {@link ColorInfo})
299 */
300 public synchronized Map<String, ColorInfo> getAllNamedColors() {
301 final Map<String, ColorInfo> all = new TreeMap<>();
302 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
303 if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
304 continue;
305 Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
[13849]306 .map(ListSetting::getValue)
[12987]307 .map(lst -> ColorInfo.fromPref(lst, false))
308 .ifPresent(info -> all.put(e.getKey(), info));
309 }
310 for (final Entry<String, Setting<?>> e : defaultsMap.entrySet()) {
311 if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
312 continue;
313 Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
[13849]314 .map(ListSetting::getValue)
[12987]315 .map(lst -> ColorInfo.fromPref(lst, true))
316 .ifPresent(infoDef -> {
317 ColorInfo info = all.get(e.getKey());
318 if (info == null) {
319 all.put(e.getKey(), infoDef);
320 } else {
321 info.setDefaultValue(infoDef.getDefaultValue());
322 }
323 });
324 }
325 return all;
326 }
327
328 /**
[7033]329 * Called after every put. In case of a problem, do nothing but output the error in log.
[8926]330 * @throws IOException if any I/O error occurs
[1169]331 */
[10380]332 public synchronized void save() throws IOException {
[10824]333 save(getPreferenceFile(), settingsMap.entrySet().stream().filter(NO_DEFAULT_SETTINGS_ENTRY), false);
[9821]334 }
[2038]335
[12374]336 /**
337 * Stores the defaults to the defaults file
338 * @throws IOException If the file could not be saved
339 */
[10380]340 public synchronized void saveDefaults() throws IOException {
[10824]341 save(getDefaultsCacheFile(), defaultsMap.entrySet().stream(), true);
[9821]342 }
[2038]343
[10824]344 protected void save(File prefFile, Stream<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
[9821]345 if (!defaults) {
346 /* currently unused, but may help to fix configuration issues in future */
[12840]347 putInt("josm.version", Version.getInstance().getVersion());
[9821]348 }
349
[4200]350 File backupFile = new File(prefFile + "_backup");
351
[2053]352 // Backup old preferences if there are old preferences
[11452]353 if (initSuccessful && prefFile.exists() && prefFile.length() > 0) {
[5874]354 Utils.copyFile(prefFile, backupFile);
[2053]355 }
356
[10952]357 try (PreferencesWriter writer = new PreferencesWriter(
358 new PrintWriter(new File(prefFile + "_tmp"), StandardCharsets.UTF_8.name()), false, defaults)) {
[9823]359 writer.write(settings);
[13647]360 } catch (SecurityException e) {
361 throw new IOException(e);
[7033]362 }
[2038]363
[2053]364 File tmpFile = new File(prefFile + "_tmp");
[5874]365 Utils.copyFile(tmpFile, prefFile);
[9296]366 Utils.deleteFile(tmpFile, marktr("Unable to delete temporary file {0}"));
[4200]367
368 setCorrectPermissions(prefFile);
369 setCorrectPermissions(backupFile);
[1169]370 }
[74]371
[8870]372 private static void setCorrectPermissions(File file) {
[12620]373 if (!file.setReadable(false, false) && Logging.isTraceEnabled()) {
374 Logging.trace(tr("Unable to set file non-readable {0}", file.getAbsolutePath()));
[8357]375 }
[12620]376 if (!file.setWritable(false, false) && Logging.isTraceEnabled()) {
377 Logging.trace(tr("Unable to set file non-writable {0}", file.getAbsolutePath()));
[8357]378 }
[12620]379 if (!file.setExecutable(false, false) && Logging.isTraceEnabled()) {
380 Logging.trace(tr("Unable to set file non-executable {0}", file.getAbsolutePath()));
[8357]381 }
[12620]382 if (!file.setReadable(true, true) && Logging.isTraceEnabled()) {
383 Logging.trace(tr("Unable to set file readable {0}", file.getAbsolutePath()));
[8357]384 }
[12620]385 if (!file.setWritable(true, true) && Logging.isTraceEnabled()) {
386 Logging.trace(tr("Unable to set file writable {0}", file.getAbsolutePath()));
[8357]387 }
[4200]388 }
389
[7315]390 /**
391 * Loads preferences from settings file.
392 * @throws IOException if any I/O error occurs while reading the file
393 * @throws SAXException if the settings file does not contain valid XML
394 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
395 */
[9217]396 protected void load() throws IOException, SAXException, XMLStreamException {
[9780]397 File pref = getPreferenceFile();
398 PreferencesReader.validateXML(pref);
[9823]399 PreferencesReader reader = new PreferencesReader(pref, false);
400 reader.parse();
[6578]401 settingsMap.clear();
[9780]402 settingsMap.putAll(reader.getSettings());
403 removeObsolete(reader.getVersion());
[1169]404 }
[116]405
[9805]406 /**
[9821]407 * Loads default preferences from default settings cache file.
408 *
409 * Discards entries older than {@link #MAX_AGE_DEFAULT_PREFERENCES}.
410 *
411 * @throws IOException if any I/O error occurs while reading the file
412 * @throws SAXException if the settings file does not contain valid XML
413 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
414 */
415 protected void loadDefaults() throws IOException, XMLStreamException, SAXException {
416 File def = getDefaultsCacheFile();
417 PreferencesReader.validateXML(def);
[9823]418 PreferencesReader reader = new PreferencesReader(def, true);
419 reader.parse();
[9821]420 defaultsMap.clear();
421 long minTime = System.currentTimeMillis() / 1000 - MAX_AGE_DEFAULT_PREFERENCES;
422 for (Entry<String, Setting<?>> e : reader.getSettings().entrySet()) {
423 if (e.getValue().getTime() >= minTime) {
424 defaultsMap.put(e.getKey(), e.getValue());
425 }
426 }
427 }
428
429 /**
[9805]430 * Loads preferences from XML reader.
431 * @param in XML reader
432 * @throws XMLStreamException if any XML stream error occurs
[9823]433 * @throws IOException if any I/O error occurs
[9805]434 */
[9823]435 public void fromXML(Reader in) throws XMLStreamException, IOException {
436 PreferencesReader reader = new PreferencesReader(in, false);
437 reader.parse();
[9780]438 settingsMap.clear();
439 settingsMap.putAll(reader.getSettings());
440 }
441
[7315]442 /**
443 * Initializes preferences.
444 * @param reset if {@code true}, current settings file is replaced by the default one
445 */
446 public void init(boolean reset) {
[9311]447 initSuccessful = false;
[1326]448 // get the preferences.
[13023]449 File prefDir = dirs.getPreferencesDirectory(false);
[1326]450 if (prefDir.exists()) {
[8510]451 if (!prefDir.isDirectory()) {
[12620]452 Logging.warn(tr("Failed to initialize preferences. Preference directory ''{0}'' is not a directory.",
[8509]453 prefDir.getAbsoluteFile()));
[13769]454 if (!GraphicsEnvironment.isHeadless()) {
455 JOptionPane.showMessageDialog(
[14153]456 MainApplication.getMainFrame(),
[13769]457 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>",
458 prefDir.getAbsoluteFile()),
459 tr("Error"),
460 JOptionPane.ERROR_MESSAGE
461 );
462 }
[1326]463 return;
464 }
[1857]465 } else {
[8443]466 if (!prefDir.mkdirs()) {
[12620]467 Logging.warn(tr("Failed to initialize preferences. Failed to create missing preference directory: {0}",
[8509]468 prefDir.getAbsoluteFile()));
[13769]469 if (!GraphicsEnvironment.isHeadless()) {
470 JOptionPane.showMessageDialog(
[14153]471 MainApplication.getMainFrame(),
[13769]472 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",
473 prefDir.getAbsoluteFile()),
474 tr("Error"),
475 JOptionPane.ERROR_MESSAGE
476 );
477 }
[2053]478 return;
479 }
[1326]480 }
481
[2053]482 File preferenceFile = getPreferenceFile();
[1326]483 try {
[2053]484 if (!preferenceFile.exists()) {
[12620]485 Logging.info(tr("Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
[5761]486 resetToDefault();
487 save();
[2053]488 } else if (reset) {
[9312]489 File backupFile = new File(prefDir, "preferences.xml.bak");
[14138]490 PlatformManager.getPlatform().rename(preferenceFile, backupFile);
[12620]491 Logging.warn(tr("Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
[2053]492 resetToDefault();
493 save();
[1857]494 }
[13838]495 } catch (IOException | InvalidPathException e) {
[12620]496 Logging.error(e);
[13769]497 if (!GraphicsEnvironment.isHeadless()) {
498 JOptionPane.showMessageDialog(
[14153]499 MainApplication.getMainFrame(),
[13769]500 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",
501 getPreferenceFile().getAbsoluteFile()),
502 tr("Error"),
503 JOptionPane.ERROR_MESSAGE
504 );
505 }
[2053]506 return;
[1326]507 }
[2053]508 try {
509 load();
[9311]510 initSuccessful = true;
[9821]511 } catch (IOException | SAXException | XMLStreamException e) {
[12620]512 Logging.error(e);
[8510]513 File backupFile = new File(prefDir, "preferences.xml.bak");
[13769]514 if (!GraphicsEnvironment.isHeadless()) {
515 JOptionPane.showMessageDialog(
[14153]516 MainApplication.getMainFrame(),
[13769]517 tr("<html>Preferences file had errors.<br> Making backup of old one to <br>{0}<br> " +
518 "and creating a new default preference file.</html>",
519 backupFile.getAbsoluteFile()),
520 tr("Error"),
521 JOptionPane.ERROR_MESSAGE
522 );
523 }
[14138]524 PlatformManager.getPlatform().rename(preferenceFile, backupFile);
[2053]525 try {
526 resetToDefault();
527 save();
[8510]528 } catch (IOException e1) {
[12620]529 Logging.error(e1);
530 Logging.warn(tr("Failed to initialize preferences. Failed to reset preference file to default: {0}", getPreferenceFile()));
[2053]531 }
532 }
[9821]533 File def = getDefaultsCacheFile();
534 if (def.exists()) {
535 try {
536 loadDefaults();
537 } catch (IOException | XMLStreamException | SAXException e) {
[12620]538 Logging.error(e);
539 Logging.warn(tr("Failed to load defaults cache file: {0}", def));
[9821]540 defaultsMap.clear();
541 if (!def.delete()) {
[12620]542 Logging.warn(tr("Failed to delete faulty defaults cache file: {0}", def));
[9821]543 }
544 }
545 }
[1326]546 }
547
[10876]548 /**
549 * Resets the preferences to their initial state. This resets all values and file associations.
550 * The default values and listeners are not removed.
551 * <p>
552 * It is meant to be called before {@link #init(boolean)}
553 * @since 10876
554 */
555 public void resetToInitialState() {
556 resetToDefault();
557 saveOnPut = true;
558 initSuccessful = false;
559 }
560
561 /**
562 * Reset all values stored in this map to the default values. This clears the preferences.
563 */
[8510]564 public final void resetToDefault() {
[6578]565 settingsMap.clear();
[1169]566 }
[172]567
[1169]568 /**
[9717]569 * Set a value for a certain setting. The changed setting is saved to the preference file immediately.
570 * Due to caching mechanisms on modern operating systems and hardware, this shouldn't be a performance problem.
[6578]571 * @param key the unique identifier for the setting
[9717]572 * @param setting the value of the setting. In case it is null, the key-value entry will be removed.
[9243]573 * @return {@code true}, if something has changed (i.e. value is different than before)
[6578]574 */
[12840]575 @Override
[7027]576 public boolean putSetting(final String key, Setting<?> setting) {
[6578]577 CheckParameterUtil.ensureParameterNotNull(key);
578 if (setting != null && setting.getValue() == null)
579 throw new IllegalArgumentException("setting argument must not have null value");
[7027]580 Setting<?> settingOld;
581 Setting<?> settingCopy = null;
[4612]582 synchronized (this) {
[6578]583 if (setting == null) {
584 settingOld = settingsMap.remove(key);
585 if (settingOld == null)
586 return false;
[4612]587 } else {
[6578]588 settingOld = settingsMap.get(key);
589 if (setting.equals(settingOld))
590 return false;
591 if (settingOld == null && setting.equals(defaultsMap.get(key)))
592 return false;
593 settingCopy = setting.copy();
594 settingsMap.put(key, settingCopy);
[4612]595 }
[7085]596 if (saveOnPut) {
597 try {
598 save();
[13838]599 } catch (IOException | InvalidPathException e) {
[13647]600 File file = getPreferenceFile();
601 try {
602 file = file.getAbsoluteFile();
603 } catch (SecurityException ex) {
604 Logging.trace(ex);
605 }
606 Logging.log(Logging.LEVEL_WARN, tr("Failed to persist preferences to ''{0}''", file), e);
[7085]607 }
[4920]608 }
[4612]609 }
610 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
[6578]611 firePreferenceChanged(key, settingOld, settingCopy);
[4612]612 return true;
[1169]613 }
[3840]614
[12374]615 /**
616 * Get a setting of any type
617 * @param key The key for the setting
618 * @param def The default value to use if it was not found
619 * @return The setting
620 */
[7027]621 public synchronized Setting<?> getSetting(String key, Setting<?> def) {
[6578]622 return getSetting(key, def, Setting.class);
623 }
624
625 /**
626 * Get settings value for a certain key and provide default a value.
627 * @param <T> the setting type
628 * @param key the identifier for the setting
[9717]629 * @param def the default value. For each call of getSetting() with a given key, the default value must be the same.
630 * <code>def</code> must not be null, but the value of <code>def</code> can be null.
[6578]631 * @param klass the setting type (same as T)
[9717]632 * @return the corresponding value if the property has been set before, {@code def} otherwise
[6578]633 */
[6792]634 @SuppressWarnings("unchecked")
[12840]635 @Override
[7027]636 public synchronized <T extends Setting<?>> T getSetting(String key, T def, Class<T> klass) {
[6578]637 CheckParameterUtil.ensureParameterNotNull(key);
638 CheckParameterUtil.ensureParameterNotNull(def);
[7027]639 Setting<?> oldDef = defaultsMap.get(key);
[9821]640 if (oldDef != null && oldDef.isNew() && oldDef.getValue() != null && def.getValue() != null && !def.equals(oldDef)) {
[12620]641 Logging.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
[4612]642 }
[6656]643 if (def.getValue() != null || oldDef == null) {
[9821]644 Setting<?> defCopy = def.copy();
645 defCopy.setTime(System.currentTimeMillis() / 1000);
646 defCopy.setNew(true);
647 defaultsMap.put(key, defCopy);
[6656]648 }
[7027]649 Setting<?> prop = settingsMap.get(key);
[6578]650 if (klass.isInstance(prop)) {
[6792]651 return (T) prop;
[6578]652 } else {
653 return def;
654 }
[4612]655 }
656
[12987]657 @Override
658 public Set<String> getKeySet() {
659 return Collections.unmodifiableSet(settingsMap.keySet());
660 }
661
[14122]662 @Override
[7027]663 public Map<String, Setting<?>> getAllSettings() {
[7005]664 return new TreeMap<>(settingsMap);
[4634]665 }
666
[12374]667 /**
668 * Gets a map of all currently known defaults
[12409]669 * @return The map (key/setting)
[12374]670 */
[7027]671 public Map<String, Setting<?>> getAllDefaults() {
[7005]672 return new TreeMap<>(defaultsMap);
[4634]673 }
674
[3908]675 /**
[6780]676 * Replies the collection of plugin site URLs from where plugin lists can be downloaded.
677 * @return the collection of plugin site URLs
[8471]678 * @see #getOnlinePluginSites
[2817]679 */
680 public Collection<String> getPluginSites() {
[14119]681 return getList("pluginmanager.sites", Collections.singletonList(Config.getUrls().getJOSMWebsite()+"/pluginicons%<?plugins=>"));
[2817]682 }
683
684 /**
[8471]685 * Returns the list of plugin sites available according to offline mode settings.
686 * @return the list of available plugin sites
687 * @since 8471
688 */
689 public Collection<String> getOnlinePluginSites() {
690 Collection<String> pluginSites = new ArrayList<>(getPluginSites());
691 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
692 try {
[14119]693 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Config.getUrls().getJOSMWebsite());
[8471]694 } catch (OfflineAccessException ex) {
[12620]695 Logging.log(Logging.LEVEL_WARN, ex);
[8471]696 it.remove();
697 }
698 }
699 return pluginSites;
700 }
701
702 /**
[2817]703 * Sets the collection of plugin site URLs.
[3194]704 *
[2817]705 * @param sites the site URLs
706 */
707 public void setPluginSites(Collection<String> sites) {
[12840]708 putList("pluginmanager.sites", new ArrayList<>(sites));
[2817]709 }
[3938]710
[9805]711 /**
712 * Returns XML describing these preferences.
713 * @param nopass if password must be excluded
714 * @return XML
715 */
[4612]716 public String toXML(boolean nopass) {
[9821]717 return toXML(settingsMap.entrySet(), nopass, false);
[9780]718 }
719
[9805]720 /**
721 * Returns XML describing the given preferences.
722 * @param settings preferences settings
723 * @param nopass if password must be excluded
[9822]724 * @param defaults true, if default values are converted to XML, false for
725 * regular preferences
[9805]726 * @return XML
727 */
[9821]728 public String toXML(Collection<Entry<String, Setting<?>>> settings, boolean nopass, boolean defaults) {
[9829]729 try (
730 StringWriter sw = new StringWriter();
[11098]731 PreferencesWriter prefWriter = new PreferencesWriter(new PrintWriter(sw), nopass, defaults)
[9829]732 ) {
733 prefWriter.write(settings);
734 sw.flush();
735 return sw.toString();
736 } catch (IOException e) {
[12620]737 Logging.error(e);
[9829]738 return null;
739 }
[3938]740 }
[4932]741
742 /**
743 * Removes obsolete preference settings. If you throw out a once-used preference
744 * setting, add it to the list here with an expiry date (written as comment). If you
745 * see something with an expiry date in the past, remove it from the list.
[9793]746 * @param loadedVersion JOSM version when the preferences file was written
[4932]747 */
[9780]748 private void removeObsolete(int loadedVersion) {
[14014]749 Logging.trace("Remove obsolete preferences for version {0}", Integer.toString(loadedVersion));
[10063]750 for (String key : OBSOLETE_PREF_KEYS) {
751 if (settingsMap.containsKey(key)) {
752 settingsMap.remove(key);
[12620]753 Logging.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
[10063]754 }
755 }
756 }
757
[7085]758 /**
759 * Enables or not the preferences file auto-save mechanism (save each time a setting is changed).
760 * This behaviour is enabled by default.
761 * @param enable if {@code true}, makes JOSM save preferences file each time a setting is changed
762 * @since 7085
763 */
764 public final void enableSaveOnPut(boolean enable) {
765 synchronized (this) {
766 saveOnPut = enable;
767 }
768 }
[74]769}
Note: See TracBrowser for help on using the repository browser.