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

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

ensures consistency of upload comment:

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