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

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

see #15310 - remove forgotten deprecated preferences stuff

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