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

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

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

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