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

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

don't display error dialog when loading preferences in headless mode

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