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

Last change on this file since 12964 was 12946, checked in by bastiK, 7 years ago

see #15410 - fix deleting color entries; do not display unchanged layer colors (this gets excessive if user opens may gpx files over time)

  • Property svn:eol-style set to native
File size: 64.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;
6
7import java.awt.Color;
8import java.awt.GraphicsEnvironment;
9import java.awt.Toolkit;
10import java.io.File;
11import java.io.IOException;
12import java.io.PrintWriter;
13import java.io.Reader;
14import java.io.StringWriter;
15import java.lang.annotation.Retention;
16import java.lang.annotation.RetentionPolicy;
17import java.lang.reflect.Field;
18import java.nio.charset.StandardCharsets;
19import java.util.AbstractMap;
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.Collections;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Map.Entry;
30import java.util.Optional;
31import java.util.ResourceBundle;
32import java.util.Set;
33import java.util.SortedMap;
34import java.util.TreeMap;
35import java.util.concurrent.TimeUnit;
36import java.util.function.Predicate;
37import java.util.regex.Matcher;
38import java.util.regex.Pattern;
39import java.util.stream.Collectors;
40import java.util.stream.Stream;
41
42import javax.swing.JOptionPane;
43import javax.xml.stream.XMLStreamException;
44
45import org.openstreetmap.josm.Main;
46import org.openstreetmap.josm.data.preferences.BooleanProperty;
47import org.openstreetmap.josm.data.preferences.ColorProperty;
48import org.openstreetmap.josm.data.preferences.DoubleProperty;
49import org.openstreetmap.josm.data.preferences.IntegerProperty;
50import org.openstreetmap.josm.spi.preferences.ListListSetting;
51import org.openstreetmap.josm.spi.preferences.ListSetting;
52import org.openstreetmap.josm.data.preferences.LongProperty;
53import org.openstreetmap.josm.spi.preferences.MapListSetting;
54import org.openstreetmap.josm.data.preferences.PreferencesReader;
55import org.openstreetmap.josm.data.preferences.PreferencesWriter;
56import org.openstreetmap.josm.spi.preferences.Setting;
57import org.openstreetmap.josm.spi.preferences.StringSetting;
58import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
59import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
60import org.openstreetmap.josm.io.OfflineAccessException;
61import org.openstreetmap.josm.io.OnlineResource;
62import org.openstreetmap.josm.spi.preferences.AbstractPreferences;
63import org.openstreetmap.josm.spi.preferences.IBaseDirectories;
64import org.openstreetmap.josm.spi.preferences.IPreferences;
65import org.openstreetmap.josm.tools.CheckParameterUtil;
66import org.openstreetmap.josm.tools.ColorHelper;
67import org.openstreetmap.josm.tools.I18n;
68import org.openstreetmap.josm.tools.ListenerList;
69import org.openstreetmap.josm.tools.Logging;
70import org.openstreetmap.josm.tools.Utils;
71import org.xml.sax.SAXException;
72
73/**
74 * This class holds all preferences for JOSM.
75 *
76 * Other classes can register their beloved properties here. All properties will be
77 * saved upon set-access.
78 *
79 * Each property is a key=setting pair, where key is a String and setting can be one of
80 * 4 types:
81 * string, list, list of lists and list of maps.
82 * In addition, each key has a unique default value that is set when the value is first
83 * accessed using one of the get...() methods. You can use the same preference
84 * key in different parts of the code, but the default value must be the same
85 * everywhere. A default value of null means, the setting has been requested, but
86 * no default value was set. This is used in advanced preferences to present a list
87 * off all possible settings.
88 *
89 * At the moment, you cannot put the empty string for string properties.
90 * put(key, "") means, the property is removed.
91 *
92 * @author imi
93 * @since 74
94 */
95public class Preferences extends AbstractPreferences implements IBaseDirectories {
96
97 private static final String COLOR_PREFIX = "color.";
98 private static final Pattern COLOR_LAYER_PATTERN = Pattern.compile("layer\\.(.+)");
99 private static final Pattern COLOR_MAPPAINT_PATTERN = Pattern.compile("mappaint\\.(.+?)\\.(.+)");
100
101 private static final String[] OBSOLETE_PREF_KEYS = {
102 "imagery.layers.addedIds", /* remove entry after June 2017 */
103 "projection", /* remove entry after Nov. 2017 */
104 "projection.sub", /* remove entry after Nov. 2017 */
105 };
106
107 private static final long MAX_AGE_DEFAULT_PREFERENCES = TimeUnit.DAYS.toSeconds(50);
108
109 /**
110 * Internal storage for the preference directory.
111 * Do not access this variable directly!
112 * @see #getPreferencesDirectory()
113 */
114 private File preferencesDir;
115
116 /**
117 * Internal storage for the cache directory.
118 */
119 private File cacheDir;
120
121 /**
122 * Internal storage for the user data directory.
123 */
124 private File userdataDir;
125
126 /**
127 * Determines if preferences file is saved each time a property is changed.
128 */
129 private boolean saveOnPut = true;
130
131 /**
132 * Maps the setting name to the current value of the setting.
133 * The map must not contain null as key or value. The mapped setting objects
134 * must not have a null value.
135 */
136 protected final SortedMap<String, Setting<?>> settingsMap = new TreeMap<>();
137
138 /**
139 * Maps the setting name to the default value of the setting.
140 * The map must not contain null as key or value. The value of the mapped
141 * setting objects can be null.
142 */
143 protected final SortedMap<String, Setting<?>> defaultsMap = new TreeMap<>();
144
145 private final Predicate<Entry<String, Setting<?>>> NO_DEFAULT_SETTINGS_ENTRY =
146 e -> !e.getValue().equals(defaultsMap.get(e.getKey()));
147
148 /**
149 * Maps color keys to human readable color name
150 */
151 protected final SortedMap<String, String> colornames = new TreeMap<>();
152
153 /**
154 * Indicates whether {@link #init(boolean)} completed successfully.
155 * Used to decide whether to write backup preference file in {@link #save()}
156 */
157 protected boolean initSuccessful;
158
159 /**
160 * Event triggered when a preference entry value changes.
161 * @deprecated use {@link org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent}
162 */
163 @Deprecated
164 public interface PreferenceChangeEvent {
165 /**
166 * Returns the preference key.
167 * @return the preference key
168 */
169 String getKey();
170
171 /**
172 * Returns the old preference value.
173 * @return the old preference value
174 */
175 Setting<?> getOldValue();
176
177 /**
178 * Returns the new preference value.
179 * @return the new preference value
180 */
181 Setting<?> getNewValue();
182 }
183
184 /**
185 * Listener to preference change events.
186 * @since 10600 (functional interface)
187 * @deprecated use {@link org.openstreetmap.josm.spi.preferences.PreferenceChangedListener}
188 */
189 @FunctionalInterface
190 @Deprecated
191 public interface PreferenceChangedListener {
192 /**
193 * Trigerred when a preference entry value changes.
194 * @param e the preference change event
195 */
196 void preferenceChanged(PreferenceChangeEvent e);
197 }
198
199 /**
200 * @deprecated private class is deprecated
201 */
202 @Deprecated
203 private static class DefaultPreferenceChangeEvent implements PreferenceChangeEvent {
204 private final String key;
205 private final Setting<?> oldValue;
206 private final Setting<?> newValue;
207
208 DefaultPreferenceChangeEvent(String key, Setting<?> oldValue, Setting<?> newValue) {
209 this.key = key;
210 this.oldValue = oldValue;
211 this.newValue = newValue;
212 }
213
214 @Override
215 public String getKey() {
216 return key;
217 }
218
219 @Override
220 public Setting<?> getOldValue() {
221 return oldValue;
222 }
223
224 @Override
225 public Setting<?> getNewValue() {
226 return newValue;
227 }
228 }
229
230 private final ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listeners = ListenerList.create();
231
232 private final HashMap<String, ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener>> keyListeners = new HashMap<>();
233
234 /**
235 * @deprecated deprecated private field
236 */
237 @Deprecated
238 private final ListenerList<Preferences.PreferenceChangedListener> listenersDeprecated = ListenerList.create();
239
240 /**
241 * @deprecated deprecated private field
242 */
243 @Deprecated
244 private final HashMap<String, ListenerList<Preferences.PreferenceChangedListener>> keyListenersDeprecated = new HashMap<>();
245
246 /**
247 * Constructs a new {@code Preferences}.
248 */
249 public Preferences() {
250 // Default constructor
251 }
252
253 /**
254 * Constructs a new {@code Preferences} from an existing instance.
255 * @param pref existing preferences to copy
256 * @since 12634
257 */
258 public Preferences(Preferences pref) {
259 settingsMap.putAll(pref.settingsMap);
260 defaultsMap.putAll(pref.defaultsMap);
261 colornames.putAll(pref.colornames);
262 }
263
264 /**
265 * Adds a new preferences listener.
266 * @param listener The listener to add
267 * @since 12881
268 */
269 @Override
270 public void addPreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
271 if (listener != null) {
272 listeners.addListener(listener);
273 }
274 }
275
276 /**
277 * Adds a new preferences listener.
278 * @param listener The listener to add
279 * @deprecated use {@link #addPreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener)}
280 */
281 @Deprecated
282 public void addPreferenceChangeListener(Preferences.PreferenceChangedListener listener) {
283 if (listener != null) {
284 listenersDeprecated.addListener(listener);
285 }
286 }
287
288 /**
289 * Removes a preferences listener.
290 * @param listener The listener to remove
291 * @since 12881
292 */
293 @Override
294 public void removePreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
295 listeners.removeListener(listener);
296 }
297
298 /**
299 * Removes a preferences listener.
300 * @param listener The listener to remove
301 * @deprecated use {@link #removePreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener)}
302 */
303 @Deprecated
304 public void removePreferenceChangeListener(Preferences.PreferenceChangedListener listener) {
305 listenersDeprecated.removeListener(listener);
306 }
307
308 /**
309 * Adds a listener that only listens to changes in one preference
310 * @param key The preference key to listen to
311 * @param listener The listener to add.
312 * @since 12881
313 */
314 @Override
315 public void addKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
316 listenersForKey(key).addListener(listener);
317 }
318
319 /**
320 * Adds a listener that only listens to changes in one preference
321 * @param key The preference key to listen to
322 * @param listener The listener to add.
323 * @since 10824
324 * @deprecated use
325 * {@link #addKeyPreferenceChangeListener(java.lang.String, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener)}
326 */
327 @Deprecated
328 public void addKeyPreferenceChangeListener(String key, Preferences.PreferenceChangedListener listener) {
329 listenersForKeyDeprecated(key).addListener(listener);
330 }
331
332 /**
333 * Adds a weak listener that only listens to changes in one preference
334 * @param key The preference key to listen to
335 * @param listener The listener to add.
336 * @since 10824
337 */
338 public void addWeakKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
339 listenersForKey(key).addWeakListener(listener);
340 }
341
342 private ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listenersForKey(String key) {
343 return keyListeners.computeIfAbsent(key, k -> ListenerList.create());
344 }
345
346 /**
347 * @deprecated deprecated private method
348 */
349 @Deprecated
350 private ListenerList<Preferences.PreferenceChangedListener> listenersForKeyDeprecated(String key) {
351 return keyListenersDeprecated.computeIfAbsent(key, k -> ListenerList.create());
352 }
353
354 /**
355 * Removes a listener that only listens to changes in one preference
356 * @param key The preference key to listen to
357 * @param listener The listener to add.
358 * @since 12881
359 */
360 @Override
361 public void removeKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
362 Optional.ofNullable(keyListeners.get(key)).orElseThrow(
363 () -> new IllegalArgumentException("There are no listeners registered for " + key))
364 .removeListener(listener);
365 }
366
367 /**
368 * Removes a listener that only listens to changes in one preference
369 * @param key The preference key to listen to
370 * @param listener The listener to add.
371 * @deprecated use
372 * {@link #removeKeyPreferenceChangeListener(java.lang.String, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener)}
373 */
374 @Deprecated
375 public void removeKeyPreferenceChangeListener(String key, Preferences.PreferenceChangedListener listener) {
376 Optional.ofNullable(keyListenersDeprecated.get(key)).orElseThrow(
377 () -> new IllegalArgumentException("There are no listeners registered for " + key))
378 .removeListener(listener);
379 }
380
381 protected void firePreferenceChanged(String key, Setting<?> oldValue, Setting<?> newValue) {
382 final org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent evt =
383 new org.openstreetmap.josm.spi.preferences.DefaultPreferenceChangeEvent(key, oldValue, newValue);
384 listeners.fireEvent(listener -> listener.preferenceChanged(evt));
385
386 ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> forKey = keyListeners.get(key);
387 if (forKey != null) {
388 forKey.fireEvent(listener -> listener.preferenceChanged(evt));
389 }
390 firePreferenceChangedDeprecated(key, oldValue, newValue);
391 }
392
393 /**
394 * @deprecated deprecated private method
395 */
396 @Deprecated
397 private void firePreferenceChangedDeprecated(String key, Setting<?> oldValue, Setting<?> newValue) {
398 final Preferences.PreferenceChangeEvent evtDeprecated = new Preferences.DefaultPreferenceChangeEvent(key, oldValue, newValue);
399 listenersDeprecated.fireEvent(listener -> listener.preferenceChanged(evtDeprecated));
400
401 ListenerList<Preferences.PreferenceChangedListener> forKeyDeprecated = keyListenersDeprecated.get(key);
402 if (forKeyDeprecated != null) {
403 forKeyDeprecated.fireEvent(listener -> listener.preferenceChanged(evtDeprecated));
404 }
405 }
406
407 /**
408 * Get the base name of the JOSM directories for preferences, cache and
409 * user data.
410 * Default value is "JOSM", unless overridden by system property "josm.dir.name".
411 * @return the base name of the JOSM directories for preferences, cache and
412 * user data
413 */
414 public String getJOSMDirectoryBaseName() {
415 String name = System.getProperty("josm.dir.name");
416 if (name != null)
417 return name;
418 else
419 return "JOSM";
420 }
421
422 /**
423 * Returns the user defined preferences directory, containing the preferences.xml file
424 * @return The user defined preferences directory, containing the preferences.xml file
425 * @since 7834
426 * @deprecated use {@link #getPreferencesDirectory(boolean)}
427 */
428 @Deprecated
429 public File getPreferencesDirectory() {
430 return getPreferencesDirectory(false);
431 }
432
433 @Override
434 public File getPreferencesDirectory(boolean createIfMissing) {
435 if (preferencesDir == null) {
436 String path;
437 path = System.getProperty("josm.pref");
438 if (path != null) {
439 preferencesDir = new File(path).getAbsoluteFile();
440 } else {
441 path = System.getProperty("josm.home");
442 if (path != null) {
443 preferencesDir = new File(path).getAbsoluteFile();
444 } else {
445 preferencesDir = Main.platform.getDefaultPrefDirectory();
446 }
447 }
448 }
449 if (createIfMissing && !preferencesDir.exists() && !preferencesDir.mkdirs()) {
450 Logging.warn(tr("Failed to create missing preferences directory: {0}", preferencesDir.getAbsoluteFile()));
451 JOptionPane.showMessageDialog(
452 Main.parent,
453 tr("<html>Failed to create missing preferences directory: {0}</html>", preferencesDir.getAbsoluteFile()),
454 tr("Error"),
455 JOptionPane.ERROR_MESSAGE
456 );
457 }
458 return preferencesDir;
459 }
460
461 /**
462 * Returns the user data directory, containing autosave, plugins, etc.
463 * Depending on the OS it may be the same directory as preferences directory.
464 * @return The user data directory, containing autosave, plugins, etc.
465 * @since 7834
466 * @deprecated use {@link #getUserDataDirectory(boolean)}
467 */
468 @Deprecated
469 public File getUserDataDirectory() {
470 return getUserDataDirectory(false);
471 }
472
473 @Override
474 public File getUserDataDirectory(boolean createIfMissing) {
475 if (userdataDir == null) {
476 String path;
477 path = System.getProperty("josm.userdata");
478 if (path != null) {
479 userdataDir = new File(path).getAbsoluteFile();
480 } else {
481 path = System.getProperty("josm.home");
482 if (path != null) {
483 userdataDir = new File(path).getAbsoluteFile();
484 } else {
485 userdataDir = Main.platform.getDefaultUserDataDirectory();
486 }
487 }
488 }
489 if (createIfMissing && !userdataDir.exists() && !userdataDir.mkdirs()) {
490 Logging.warn(tr("Failed to create missing user data directory: {0}", userdataDir.getAbsoluteFile()));
491 JOptionPane.showMessageDialog(
492 Main.parent,
493 tr("<html>Failed to create missing user data directory: {0}</html>", userdataDir.getAbsoluteFile()),
494 tr("Error"),
495 JOptionPane.ERROR_MESSAGE
496 );
497 }
498 return userdataDir;
499 }
500
501 /**
502 * Returns the user preferences file (preferences.xml).
503 * @return The user preferences file (preferences.xml)
504 */
505 public File getPreferenceFile() {
506 return new File(getPreferencesDirectory(false), "preferences.xml");
507 }
508
509 /**
510 * Returns the cache file for default preferences.
511 * @return the cache file for default preferences
512 */
513 public File getDefaultsCacheFile() {
514 return new File(getCacheDirectory(true), "default_preferences.xml");
515 }
516
517 /**
518 * Returns the user plugin directory.
519 * @return The user plugin directory
520 */
521 public File getPluginsDirectory() {
522 return new File(getUserDataDirectory(false), "plugins");
523 }
524
525 /**
526 * Get the directory where cached content of any kind should be stored.
527 *
528 * If the directory doesn't exist on the file system, it will be created by this method.
529 *
530 * @return the cache directory
531 * @deprecated use {@link #getCacheDirectory(boolean)}
532 */
533 @Deprecated
534 public File getCacheDirectory() {
535 return getCacheDirectory(true);
536 }
537
538 @Override
539 public File getCacheDirectory(boolean createIfMissing) {
540 if (cacheDir == null) {
541 String path = System.getProperty("josm.cache");
542 if (path != null) {
543 cacheDir = new File(path).getAbsoluteFile();
544 } else {
545 path = System.getProperty("josm.home");
546 if (path != null) {
547 cacheDir = new File(path, "cache");
548 } else {
549 path = get("cache.folder", null);
550 if (path != null) {
551 cacheDir = new File(path).getAbsoluteFile();
552 } else {
553 cacheDir = Main.platform.getDefaultCacheDirectory();
554 }
555 }
556 }
557 }
558 if (createIfMissing && !cacheDir.exists() && !cacheDir.mkdirs()) {
559 Logging.warn(tr("Failed to create missing cache directory: {0}", cacheDir.getAbsoluteFile()));
560 JOptionPane.showMessageDialog(
561 Main.parent,
562 tr("<html>Failed to create missing cache directory: {0}</html>", cacheDir.getAbsoluteFile()),
563 tr("Error"),
564 JOptionPane.ERROR_MESSAGE
565 );
566 }
567 return cacheDir;
568 }
569
570 private static void addPossibleResourceDir(Set<String> locations, String s) {
571 if (s != null) {
572 if (!s.endsWith(File.separator)) {
573 s += File.separator;
574 }
575 locations.add(s);
576 }
577 }
578
579 /**
580 * Returns a set of all existing directories where resources could be stored.
581 * @return A set of all existing directories where resources could be stored.
582 */
583 public Collection<String> getAllPossiblePreferenceDirs() {
584 Set<String> locations = new HashSet<>();
585 addPossibleResourceDir(locations, getPreferencesDirectory(false).getPath());
586 addPossibleResourceDir(locations, getUserDataDirectory(false).getPath());
587 addPossibleResourceDir(locations, System.getenv("JOSM_RESOURCES"));
588 addPossibleResourceDir(locations, System.getProperty("josm.resources"));
589 if (Main.isPlatformWindows()) {
590 String appdata = System.getenv("APPDATA");
591 if (appdata != null && System.getenv("ALLUSERSPROFILE") != null
592 && appdata.lastIndexOf(File.separator) != -1) {
593 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
594 locations.add(new File(new File(System.getenv("ALLUSERSPROFILE"),
595 appdata), "JOSM").getPath());
596 }
597 } else {
598 locations.add("/usr/local/share/josm/");
599 locations.add("/usr/local/lib/josm/");
600 locations.add("/usr/share/josm/");
601 locations.add("/usr/lib/josm/");
602 }
603 return locations;
604 }
605
606 /**
607 * Gets all normal (string) settings that have a key starting with the prefix
608 * @param prefix The start of the key
609 * @return The key names of the settings
610 */
611 public synchronized Map<String, String> getAllPrefix(final String prefix) {
612 final Map<String, String> all = new TreeMap<>();
613 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
614 if (e.getKey().startsWith(prefix) && (e.getValue() instanceof StringSetting)) {
615 all.put(e.getKey(), ((StringSetting) e.getValue()).getValue());
616 }
617 }
618 return all;
619 }
620
621 /**
622 * Gets all list settings that have a key starting with the prefix
623 * @param prefix The start of the key
624 * @return The key names of the list settings
625 */
626 public synchronized List<String> getAllPrefixCollectionKeys(final String prefix) {
627 final List<String> all = new LinkedList<>();
628 for (Map.Entry<String, Setting<?>> entry : settingsMap.entrySet()) {
629 if (entry.getKey().startsWith(prefix) && entry.getValue() instanceof ListSetting) {
630 all.add(entry.getKey());
631 }
632 }
633 return all;
634 }
635
636 /**
637 * Gets all known colors (preferences starting with the color prefix)
638 * @return All colors
639 */
640 public synchronized Map<String, String> getAllColors() {
641 final Map<String, String> all = new TreeMap<>();
642 for (final Entry<String, Setting<?>> e : defaultsMap.entrySet()) {
643 if (e.getKey().startsWith(COLOR_PREFIX) && e.getValue() instanceof StringSetting) {
644 if (e.getKey().startsWith(COLOR_PREFIX+"layer."))
645 continue; // do not add unchanged layer colors
646 StringSetting d = (StringSetting) e.getValue();
647 if (d.getValue() != null) {
648 all.put(e.getKey().substring(6), d.getValue());
649 }
650 }
651 }
652 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
653 if (e.getKey().startsWith(COLOR_PREFIX) && (e.getValue() instanceof StringSetting)) {
654 all.put(e.getKey().substring(6), ((StringSetting) e.getValue()).getValue());
655 }
656 }
657 return all;
658 }
659
660 /**
661 * Gets an boolean that may be specialized
662 * @param key The basic key
663 * @param specName The sub-key to append to the key
664 * @param def The default value
665 * @return The boolean value or the default value if it could not be parsed
666 * @deprecated use {@link PreferencesUtils#getBoolean(IPreferences, String, String, boolean)}
667 */
668 @Deprecated
669 public synchronized boolean getBoolean(final String key, final String specName, final boolean def) {
670 boolean generic = getBoolean(key, def);
671 String skey = key+'.'+specName;
672 Setting<?> prop = settingsMap.get(skey);
673 if (prop instanceof StringSetting)
674 return Boolean.parseBoolean(((StringSetting) prop).getValue());
675 else
676 return generic;
677 }
678
679 /**
680 * Set a boolean value for a certain setting.
681 * @param key the unique identifier for the setting
682 * @param value The new value
683 * @return {@code true}, if something has changed (i.e. value is different than before)
684 * @see BooleanProperty
685 * @deprecated use {@link IPreferences#putBoolean(String, boolean)}
686 */
687 @Deprecated
688 public boolean put(final String key, final boolean value) {
689 return put(key, Boolean.toString(value));
690 }
691
692 /**
693 * Set a boolean value for a certain setting.
694 * @param key the unique identifier for the setting
695 * @param value The new value
696 * @return {@code true}, if something has changed (i.e. value is different than before)
697 * @see IntegerProperty#put(Integer)
698 * @deprecated use {@link IPreferences#putInt(String, int)}
699 */
700 @Deprecated
701 public boolean putInteger(final String key, final Integer value) {
702 return put(key, Integer.toString(value));
703 }
704
705 /**
706 * Set a boolean value for a certain setting.
707 * @param key the unique identifier for the setting
708 * @param value The new value
709 * @return {@code true}, if something has changed (i.e. value is different than before)
710 * @see DoubleProperty#put(Double)
711 * @deprecated use {@link IPreferences#putDouble(java.lang.String, double)}
712 */
713 @Deprecated
714 public boolean putDouble(final String key, final Double value) {
715 return put(key, Double.toString(value));
716 }
717
718 /**
719 * Set a boolean value for a certain setting.
720 * @param key the unique identifier for the setting
721 * @param value The new value
722 * @return {@code true}, if something has changed (i.e. value is different than before)
723 * @see LongProperty#put(Long)
724 * @deprecated use {@link IPreferences#putLong(java.lang.String, long)}
725 */
726 @Deprecated
727 public boolean putLong(final String key, final Long value) {
728 return put(key, Long.toString(value));
729 }
730
731 /**
732 * Called after every put. In case of a problem, do nothing but output the error in log.
733 * @throws IOException if any I/O error occurs
734 */
735 public synchronized void save() throws IOException {
736 save(getPreferenceFile(), settingsMap.entrySet().stream().filter(NO_DEFAULT_SETTINGS_ENTRY), false);
737 }
738
739 /**
740 * Stores the defaults to the defaults file
741 * @throws IOException If the file could not be saved
742 */
743 public synchronized void saveDefaults() throws IOException {
744 save(getDefaultsCacheFile(), defaultsMap.entrySet().stream(), true);
745 }
746
747 protected void save(File prefFile, Stream<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
748 if (!defaults) {
749 /* currently unused, but may help to fix configuration issues in future */
750 putInt("josm.version", Version.getInstance().getVersion());
751
752 updateSystemProperties();
753 }
754
755 File backupFile = new File(prefFile + "_backup");
756
757 // Backup old preferences if there are old preferences
758 if (initSuccessful && prefFile.exists() && prefFile.length() > 0) {
759 Utils.copyFile(prefFile, backupFile);
760 }
761
762 try (PreferencesWriter writer = new PreferencesWriter(
763 new PrintWriter(new File(prefFile + "_tmp"), StandardCharsets.UTF_8.name()), false, defaults)) {
764 writer.write(settings);
765 }
766
767 File tmpFile = new File(prefFile + "_tmp");
768 Utils.copyFile(tmpFile, prefFile);
769 Utils.deleteFile(tmpFile, marktr("Unable to delete temporary file {0}"));
770
771 setCorrectPermissions(prefFile);
772 setCorrectPermissions(backupFile);
773 }
774
775 private static void setCorrectPermissions(File file) {
776 if (!file.setReadable(false, false) && Logging.isTraceEnabled()) {
777 Logging.trace(tr("Unable to set file non-readable {0}", file.getAbsolutePath()));
778 }
779 if (!file.setWritable(false, false) && Logging.isTraceEnabled()) {
780 Logging.trace(tr("Unable to set file non-writable {0}", file.getAbsolutePath()));
781 }
782 if (!file.setExecutable(false, false) && Logging.isTraceEnabled()) {
783 Logging.trace(tr("Unable to set file non-executable {0}", file.getAbsolutePath()));
784 }
785 if (!file.setReadable(true, true) && Logging.isTraceEnabled()) {
786 Logging.trace(tr("Unable to set file readable {0}", file.getAbsolutePath()));
787 }
788 if (!file.setWritable(true, true) && Logging.isTraceEnabled()) {
789 Logging.trace(tr("Unable to set file writable {0}", file.getAbsolutePath()));
790 }
791 }
792
793 /**
794 * Loads preferences from settings file.
795 * @throws IOException if any I/O error occurs while reading the file
796 * @throws SAXException if the settings file does not contain valid XML
797 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
798 */
799 protected void load() throws IOException, SAXException, XMLStreamException {
800 File pref = getPreferenceFile();
801 PreferencesReader.validateXML(pref);
802 PreferencesReader reader = new PreferencesReader(pref, false);
803 reader.parse();
804 settingsMap.clear();
805 settingsMap.putAll(reader.getSettings());
806 updateSystemProperties();
807 removeObsolete(reader.getVersion());
808 }
809
810 /**
811 * Loads default preferences from default settings cache file.
812 *
813 * Discards entries older than {@link #MAX_AGE_DEFAULT_PREFERENCES}.
814 *
815 * @throws IOException if any I/O error occurs while reading the file
816 * @throws SAXException if the settings file does not contain valid XML
817 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
818 */
819 protected void loadDefaults() throws IOException, XMLStreamException, SAXException {
820 File def = getDefaultsCacheFile();
821 PreferencesReader.validateXML(def);
822 PreferencesReader reader = new PreferencesReader(def, true);
823 reader.parse();
824 defaultsMap.clear();
825 long minTime = System.currentTimeMillis() / 1000 - MAX_AGE_DEFAULT_PREFERENCES;
826 for (Entry<String, Setting<?>> e : reader.getSettings().entrySet()) {
827 if (e.getValue().getTime() >= minTime) {
828 defaultsMap.put(e.getKey(), e.getValue());
829 }
830 }
831 }
832
833 /**
834 * Loads preferences from XML reader.
835 * @param in XML reader
836 * @throws XMLStreamException if any XML stream error occurs
837 * @throws IOException if any I/O error occurs
838 */
839 public void fromXML(Reader in) throws XMLStreamException, IOException {
840 PreferencesReader reader = new PreferencesReader(in, false);
841 reader.parse();
842 settingsMap.clear();
843 settingsMap.putAll(reader.getSettings());
844 }
845
846 /**
847 * Initializes preferences.
848 * @param reset if {@code true}, current settings file is replaced by the default one
849 */
850 public void init(boolean reset) {
851 initSuccessful = false;
852 // get the preferences.
853 File prefDir = getPreferencesDirectory(false);
854 if (prefDir.exists()) {
855 if (!prefDir.isDirectory()) {
856 Logging.warn(tr("Failed to initialize preferences. Preference directory ''{0}'' is not a directory.",
857 prefDir.getAbsoluteFile()));
858 JOptionPane.showMessageDialog(
859 Main.parent,
860 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>",
861 prefDir.getAbsoluteFile()),
862 tr("Error"),
863 JOptionPane.ERROR_MESSAGE
864 );
865 return;
866 }
867 } else {
868 if (!prefDir.mkdirs()) {
869 Logging.warn(tr("Failed to initialize preferences. Failed to create missing preference directory: {0}",
870 prefDir.getAbsoluteFile()));
871 JOptionPane.showMessageDialog(
872 Main.parent,
873 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",
874 prefDir.getAbsoluteFile()),
875 tr("Error"),
876 JOptionPane.ERROR_MESSAGE
877 );
878 return;
879 }
880 }
881
882 File preferenceFile = getPreferenceFile();
883 try {
884 if (!preferenceFile.exists()) {
885 Logging.info(tr("Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
886 resetToDefault();
887 save();
888 } else if (reset) {
889 File backupFile = new File(prefDir, "preferences.xml.bak");
890 Main.platform.rename(preferenceFile, backupFile);
891 Logging.warn(tr("Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
892 resetToDefault();
893 save();
894 }
895 } catch (IOException e) {
896 Logging.error(e);
897 JOptionPane.showMessageDialog(
898 Main.parent,
899 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",
900 getPreferenceFile().getAbsoluteFile()),
901 tr("Error"),
902 JOptionPane.ERROR_MESSAGE
903 );
904 return;
905 }
906 try {
907 load();
908 initSuccessful = true;
909 } catch (IOException | SAXException | XMLStreamException e) {
910 Logging.error(e);
911 File backupFile = new File(prefDir, "preferences.xml.bak");
912 JOptionPane.showMessageDialog(
913 Main.parent,
914 tr("<html>Preferences file had errors.<br> Making backup of old one to <br>{0}<br> " +
915 "and creating a new default preference file.</html>",
916 backupFile.getAbsoluteFile()),
917 tr("Error"),
918 JOptionPane.ERROR_MESSAGE
919 );
920 Main.platform.rename(preferenceFile, backupFile);
921 try {
922 resetToDefault();
923 save();
924 } catch (IOException e1) {
925 Logging.error(e1);
926 Logging.warn(tr("Failed to initialize preferences. Failed to reset preference file to default: {0}", getPreferenceFile()));
927 }
928 }
929 File def = getDefaultsCacheFile();
930 if (def.exists()) {
931 try {
932 loadDefaults();
933 } catch (IOException | XMLStreamException | SAXException e) {
934 Logging.error(e);
935 Logging.warn(tr("Failed to load defaults cache file: {0}", def));
936 defaultsMap.clear();
937 if (!def.delete()) {
938 Logging.warn(tr("Failed to delete faulty defaults cache file: {0}", def));
939 }
940 }
941 }
942 }
943
944 /**
945 * Resets the preferences to their initial state. This resets all values and file associations.
946 * The default values and listeners are not removed.
947 * <p>
948 * It is meant to be called before {@link #init(boolean)}
949 * @since 10876
950 */
951 public void resetToInitialState() {
952 resetToDefault();
953 preferencesDir = null;
954 cacheDir = null;
955 userdataDir = null;
956 saveOnPut = true;
957 initSuccessful = false;
958 }
959
960 /**
961 * Reset all values stored in this map to the default values. This clears the preferences.
962 */
963 public final void resetToDefault() {
964 settingsMap.clear();
965 }
966
967 /**
968 * Convenience method for accessing colour preferences.
969 * <p>
970 * To be removed: end of 2016
971 *
972 * @param colName name of the colour
973 * @param def default value
974 * @return a Color object for the configured colour, or the default value if none configured.
975 * @deprecated Use a {@link ColorProperty} instead.
976 */
977 @Deprecated
978 public synchronized Color getColor(String colName, Color def) {
979 return getColor(colName, null, def);
980 }
981
982 /* only for preferences */
983 public synchronized String getColorName(String o) {
984 Matcher m = COLOR_LAYER_PATTERN.matcher(o);
985 if (m.matches()) {
986 return tr("Layer: {0}", tr(I18n.escape(m.group(1))));
987 }
988 String fullKey = COLOR_PREFIX + o;
989 if (colornames.containsKey(fullKey)) {
990 String name = colornames.get(fullKey);
991 Matcher m2 = COLOR_MAPPAINT_PATTERN.matcher(name);
992 if (m2.matches()) {
993 return tr("Paint style {0}: {1}", tr(I18n.escape(m2.group(1))), tr(I18n.escape(m2.group(2))));
994 } else {
995 return tr(I18n.escape(colornames.get(fullKey)));
996 }
997 } else {
998 return fullKey;
999 }
1000 }
1001
1002 /**
1003 * Convenience method for accessing colour preferences.
1004 * <p>
1005 * To be removed: end of 2016
1006 * @param colName name of the colour
1007 * @param specName name of the special colour settings
1008 * @param def default value
1009 * @return a Color object for the configured colour, or the default value if none configured.
1010 * @deprecated Use a {@link ColorProperty} instead.
1011 * You can replace this by: <code>new ColorProperty(colName, def).getChildColor(specName)</code>
1012 */
1013 @Deprecated
1014 public synchronized Color getColor(String colName, String specName, Color def) {
1015 String colKey = ColorProperty.getColorKey(colName);
1016 registerColor(colKey, colName);
1017 String colStr = specName != null ? get(COLOR_PREFIX+specName) : "";
1018 if (colStr.isEmpty()) {
1019 colStr = get(colKey, ColorHelper.color2html(def, true));
1020 }
1021 if (colStr != null && !colStr.isEmpty()) {
1022 return ColorHelper.html2color(colStr);
1023 } else {
1024 return def;
1025 }
1026 }
1027
1028 /**
1029 * Registers a color name conversion for the global color registry.
1030 * @param colKey The key
1031 * @param colName The name of the color.
1032 * @since 10824
1033 */
1034 public void registerColor(String colKey, String colName) {
1035 if (!colKey.equals(colName)) {
1036 colornames.put(colKey, colName);
1037 }
1038 }
1039
1040 /**
1041 * Gets the default color that was registered with the preference
1042 * @param colKey The color name
1043 * @return The color
1044 */
1045 public synchronized Color getDefaultColor(String colKey) {
1046 StringSetting col = Utils.cast(defaultsMap.get(COLOR_PREFIX+colKey), StringSetting.class);
1047 String colStr = col == null ? null : col.getValue();
1048 return colStr == null || colStr.isEmpty() ? null : ColorHelper.html2color(colStr);
1049 }
1050
1051 /**
1052 * Stores a color
1053 * @param colKey The color name
1054 * @param val The color
1055 * @return true if the setting was modified
1056 * @see ColorProperty#put(Color)
1057 */
1058 public synchronized boolean putColor(String colKey, Color val) {
1059 return put(COLOR_PREFIX+colKey, val != null ? ColorHelper.color2html(val, true) : null);
1060 }
1061
1062 /**
1063 * Gets an integer preference
1064 * @param key The preference key
1065 * @param def The default value to use
1066 * @return The integer
1067 * @see IntegerProperty#get()
1068 * @deprecated use {@link IPreferences#getInt(String, int)}
1069 */
1070 @Deprecated
1071 public synchronized int getInteger(String key, int def) {
1072 String v = get(key, Integer.toString(def));
1073 if (v.isEmpty())
1074 return def;
1075
1076 try {
1077 return Integer.parseInt(v);
1078 } catch (NumberFormatException e) {
1079 // fall out
1080 Logging.trace(e);
1081 }
1082 return def;
1083 }
1084
1085 /**
1086 * Gets an integer that may be specialized
1087 * @param key The basic key
1088 * @param specName The sub-key to append to the key
1089 * @param def The default value
1090 * @return The integer value or the default value if it could not be parsed
1091 * @deprecated use {@link PreferencesUtils#getInteger(IPreferences, String, String, int)}
1092 */
1093 @Deprecated
1094 public synchronized int getInteger(String key, String specName, int def) {
1095 String v = get(key+'.'+specName);
1096 if (v.isEmpty())
1097 v = get(key, Integer.toString(def));
1098 if (v.isEmpty())
1099 return def;
1100
1101 try {
1102 return Integer.parseInt(v);
1103 } catch (NumberFormatException e) {
1104 // fall out
1105 Logging.trace(e);
1106 }
1107 return def;
1108 }
1109
1110 /**
1111 * Get a list of values for a certain key
1112 * @param key the identifier for the setting
1113 * @param def the default value.
1114 * @return the corresponding value if the property has been set before, {@code def} otherwise
1115 * @deprecated use {@link IPreferences#getList(java.lang.String, java.util.List)}
1116 */
1117 @Deprecated
1118 public Collection<String> getCollection(String key, Collection<String> def) {
1119 return getSetting(key, ListSetting.create(def), ListSetting.class).getValue();
1120 }
1121
1122 /**
1123 * Get a list of values for a certain key
1124 * @param key the identifier for the setting
1125 * @return the corresponding value if the property has been set before, an empty collection otherwise.
1126 * @deprecated use {@link IPreferences#getList(java.lang.String)}
1127 */
1128 @Deprecated
1129 public Collection<String> getCollection(String key) {
1130 Collection<String> val = getList(key, null);
1131 return val == null ? Collections.<String>emptyList() : val;
1132 }
1133
1134 /**
1135 * Removes a value from a given String collection
1136 * @param key The preference key the collection is stored with
1137 * @param value The value that should be removed in the collection
1138 * @see #getList(String)
1139 * @deprecated use {@link PreferencesUtils#removeFromList(IPreferences, String, String)}
1140 */
1141 @Deprecated
1142 public synchronized void removeFromCollection(String key, String value) {
1143 List<String> a = new ArrayList<>(getList(key, Collections.<String>emptyList()));
1144 a.remove(value);
1145 putList(key, a);
1146 }
1147
1148 /**
1149 * Set a value for a certain setting. The changed setting is saved to the preference file immediately.
1150 * Due to caching mechanisms on modern operating systems and hardware, this shouldn't be a performance problem.
1151 * @param key the unique identifier for the setting
1152 * @param setting the value of the setting. In case it is null, the key-value entry will be removed.
1153 * @return {@code true}, if something has changed (i.e. value is different than before)
1154 */
1155 @Override
1156 public boolean putSetting(final String key, Setting<?> setting) {
1157 CheckParameterUtil.ensureParameterNotNull(key);
1158 if (setting != null && setting.getValue() == null)
1159 throw new IllegalArgumentException("setting argument must not have null value");
1160 Setting<?> settingOld;
1161 Setting<?> settingCopy = null;
1162 synchronized (this) {
1163 if (setting == null) {
1164 settingOld = settingsMap.remove(key);
1165 if (settingOld == null)
1166 return false;
1167 } else {
1168 settingOld = settingsMap.get(key);
1169 if (setting.equals(settingOld))
1170 return false;
1171 if (settingOld == null && setting.equals(defaultsMap.get(key)))
1172 return false;
1173 settingCopy = setting.copy();
1174 settingsMap.put(key, settingCopy);
1175 }
1176 if (saveOnPut) {
1177 try {
1178 save();
1179 } catch (IOException e) {
1180 Logging.log(Logging.LEVEL_WARN, tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()), e);
1181 }
1182 }
1183 }
1184 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
1185 firePreferenceChanged(key, settingOld, settingCopy);
1186 return true;
1187 }
1188
1189 /**
1190 * Get a setting of any type
1191 * @param key The key for the setting
1192 * @param def The default value to use if it was not found
1193 * @return The setting
1194 */
1195 public synchronized Setting<?> getSetting(String key, Setting<?> def) {
1196 return getSetting(key, def, Setting.class);
1197 }
1198
1199 /**
1200 * Get settings value for a certain key and provide default a value.
1201 * @param <T> the setting type
1202 * @param key the identifier for the setting
1203 * @param def the default value. For each call of getSetting() with a given key, the default value must be the same.
1204 * <code>def</code> must not be null, but the value of <code>def</code> can be null.
1205 * @param klass the setting type (same as T)
1206 * @return the corresponding value if the property has been set before, {@code def} otherwise
1207 */
1208 @SuppressWarnings("unchecked")
1209 @Override
1210 public synchronized <T extends Setting<?>> T getSetting(String key, T def, Class<T> klass) {
1211 CheckParameterUtil.ensureParameterNotNull(key);
1212 CheckParameterUtil.ensureParameterNotNull(def);
1213 Setting<?> oldDef = defaultsMap.get(key);
1214 if (oldDef != null && oldDef.isNew() && oldDef.getValue() != null && def.getValue() != null && !def.equals(oldDef)) {
1215 Logging.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
1216 }
1217 if (def.getValue() != null || oldDef == null) {
1218 Setting<?> defCopy = def.copy();
1219 defCopy.setTime(System.currentTimeMillis() / 1000);
1220 defCopy.setNew(true);
1221 defaultsMap.put(key, defCopy);
1222 }
1223 Setting<?> prop = settingsMap.get(key);
1224 if (klass.isInstance(prop)) {
1225 return (T) prop;
1226 } else {
1227 return def;
1228 }
1229 }
1230
1231 /**
1232 * Put a collection.
1233 * @param key key
1234 * @param value value
1235 * @return {@code true}, if something has changed (i.e. value is different than before)
1236 * @deprecated use {@link IPreferences#putList(java.lang.String, java.util.List)}
1237 */
1238 @Deprecated
1239 public boolean putCollection(String key, Collection<String> value) {
1240 return putSetting(key, value == null ? null : ListSetting.create(value));
1241 }
1242
1243 /**
1244 * Saves at most {@code maxsize} items of collection {@code val}.
1245 * @param key key
1246 * @param maxsize max number of items to save
1247 * @param val value
1248 * @return {@code true}, if something has changed (i.e. value is different than before)
1249 * @deprecated use {@link PreferencesUtils#putListBounded(IPreferences, String, int, List)}
1250 */
1251 @Deprecated
1252 public boolean putCollectionBounded(String key, int maxsize, Collection<String> val) {
1253 List<String> newCollection = new ArrayList<>(Math.min(maxsize, val.size()));
1254 for (String i : val) {
1255 if (newCollection.size() >= maxsize) {
1256 break;
1257 }
1258 newCollection.add(i);
1259 }
1260 return putList(key, newCollection);
1261 }
1262
1263 /**
1264 * Used to read a 2-dimensional array of strings from the preference file.
1265 * If not a single entry could be found, <code>def</code> is returned.
1266 * @param key preference key
1267 * @param def default array value
1268 * @return array value
1269 * @deprecated use {@link #getListOfLists(java.lang.String, java.util.List)}
1270 */
1271 @Deprecated
1272 @SuppressWarnings({ "unchecked", "rawtypes" })
1273 public synchronized Collection<Collection<String>> getArray(String key, Collection<Collection<String>> def) {
1274 ListListSetting val = getSetting(key, ListListSetting.create(def), ListListSetting.class);
1275 return (Collection) val.getValue();
1276 }
1277
1278 /**
1279 * Gets a collection of string collections for the given key
1280 * @param key The key
1281 * @return The collection of string collections or an empty collection as default
1282 * @deprecated use {@link IPreferences#getListOfLists(java.lang.String)}
1283 */
1284 @Deprecated
1285 public Collection<Collection<String>> getArray(String key) {
1286 Collection<Collection<String>> res = getArray(key, null);
1287 return res == null ? Collections.<Collection<String>>emptyList() : res;
1288 }
1289
1290 /**
1291 * Put an array.
1292 * @param key key
1293 * @param value value
1294 * @return {@code true}, if something has changed (i.e. value is different than before)
1295 * @deprecated use {@link IPreferences#putListOfLists(java.lang.String, java.util.List)}
1296 */
1297 @Deprecated
1298 public boolean putArray(String key, Collection<Collection<String>> value) {
1299 return putSetting(key, value == null ? null : ListListSetting.create(value));
1300 }
1301
1302 /**
1303 * Gets a collection of key/value maps.
1304 * @param key The key to search at
1305 * @param def The default value to use
1306 * @return The stored value or the default one if it could not be parsed
1307 * @deprecated use {@link IPreferences#getListOfMaps(java.lang.String, java.util.List)}
1308 */
1309 @Deprecated
1310 public Collection<Map<String, String>> getListOfStructs(String key, Collection<Map<String, String>> def) {
1311 return getSetting(key, new MapListSetting(def == null ? null : new ArrayList<>(def)), MapListSetting.class).getValue();
1312 }
1313
1314 /**
1315 * Stores a list of structs
1316 * @param key The key to store the list in
1317 * @param value A list of key/value maps
1318 * @return <code>true</code> if the value was changed
1319 * @see #getListOfMaps(java.lang.String, java.util.List)
1320 * @deprecated use {@link IPreferences#putListOfMaps(java.lang.String, java.util.List)}
1321 */
1322 @Deprecated
1323 public boolean putListOfStructs(String key, Collection<Map<String, String>> value) {
1324 return putSetting(key, value == null ? null : new MapListSetting(new ArrayList<>(value)));
1325 }
1326
1327 /**
1328 * Annotation used for converting objects to String Maps and vice versa.
1329 * Indicates that a certain field should be considered in the conversion process. Otherwise it is ignored.
1330 *
1331 * @see #serializeStruct(java.lang.Object, java.lang.Class)
1332 * @see #deserializeStruct(java.util.Map, java.lang.Class)
1333 * @deprecated use {@link StructUtils.StructEntry}
1334 */
1335 @Deprecated
1336 @Retention(RetentionPolicy.RUNTIME) // keep annotation at runtime
1337 public @interface pref { }
1338
1339 /**
1340 * Annotation used for converting objects to String Maps.
1341 * Indicates that a certain field should be written to the map, even if the value is the same as the default value.
1342 *
1343 * @see #serializeStruct(java.lang.Object, java.lang.Class)
1344 * @deprecated use {@link StructUtils.WriteExplicitly}
1345 */
1346 @Deprecated
1347 @Retention(RetentionPolicy.RUNTIME) // keep annotation at runtime
1348 public @interface writeExplicitly { }
1349
1350 /**
1351 * Get a list of hashes which are represented by a struct-like class.
1352 * Possible properties are given by fields of the class klass that have the @pref annotation.
1353 * Default constructor is used to initialize the struct objects, properties then override some of these default values.
1354 * @param <T> klass type
1355 * @param key main preference key
1356 * @param klass The struct class
1357 * @return a list of objects of type T or an empty list if nothing was found
1358 * @deprecated use {@link StructUtils#getListOfStructs(IPreferences, String, Class)}
1359 */
1360 @Deprecated
1361 public <T> List<T> getListOfStructs(String key, Class<T> klass) {
1362 return StructUtils.getListOfStructs(this, key, klass);
1363 }
1364
1365 /**
1366 * same as above, but returns def if nothing was found
1367 * @param <T> klass type
1368 * @param key main preference key
1369 * @param def default value
1370 * @param klass The struct class
1371 * @return a list of objects of type T or {@code def} if nothing was found
1372 * @deprecated use {@link StructUtils#getListOfStructs(IPreferences, String, Collection, Class)}
1373 */
1374 @Deprecated
1375 public <T> List<T> getListOfStructs(String key, Collection<T> def, Class<T> klass) {
1376 return StructUtils.getListOfStructs(this, key, def, klass);
1377 }
1378
1379 /**
1380 * Convenience method that saves a MapListSetting which is provided as a collection of objects.
1381 *
1382 * Each object is converted to a <code>Map&lt;String, String&gt;</code> using the fields with {@link pref} annotation.
1383 * The field name is the key and the value will be converted to a string.
1384 *
1385 * Considers only fields that have the @pref annotation.
1386 * In addition it does not write fields with null values. (Thus they are cleared)
1387 * Default values are given by the field values after default constructor has been called.
1388 * Fields equal to the default value are not written unless the field has the @writeExplicitly annotation.
1389 * @param <T> the class,
1390 * @param key main preference key
1391 * @param val the list that is supposed to be saved
1392 * @param klass The struct class
1393 * @return true if something has changed
1394 * @deprecated use {@link StructUtils#putListOfStructs(IPreferences, String, Collection, Class)}
1395 */
1396 @Deprecated
1397 public <T> boolean putListOfStructs(String key, Collection<T> val, Class<T> klass) {
1398 return StructUtils.putListOfStructs(this, key, val, klass);
1399 }
1400
1401 /**
1402 * Convert an object to a String Map, by using field names and values as map key and value.
1403 *
1404 * The field value is converted to a String.
1405 *
1406 * Only fields with annotation {@link pref} are taken into account.
1407 *
1408 * Fields will not be written to the map if the value is null or unchanged
1409 * (compared to an object created with the no-arg-constructor).
1410 * The {@link writeExplicitly} annotation overrides this behavior, i.e. the default value will also be written.
1411 *
1412 * @param <T> the class of the object <code>struct</code>
1413 * @param struct the object to be converted
1414 * @param klass the class T
1415 * @return the resulting map (same data content as <code>struct</code>)
1416 * @deprecated use {@link StructUtils#serializeStruct(java.lang.Object, java.lang.Class)}
1417 */
1418 @Deprecated
1419 public static <T> Map<String, String> serializeStruct(T struct, Class<T> klass) {
1420 return StructUtils.serializeStruct(struct, klass);
1421 }
1422
1423 /**
1424 * Converts a String-Map to an object of a certain class, by comparing map keys to field names of the class and assigning
1425 * map values to the corresponding fields.
1426 *
1427 * The map value (a String) is converted to the field type. Supported types are: boolean, Boolean, int, Integer, double,
1428 * Double, String, Map&lt;String, String&gt; and Map&lt;String, List&lt;String&gt;&gt;.
1429 *
1430 * Only fields with annotation {@link pref} are taken into account.
1431 * @param <T> the class
1432 * @param hash the string map with initial values
1433 * @param klass the class T
1434 * @return an object of class T, initialized as described above
1435 * @deprecated use {@link StructUtils#deserializeStruct(java.util.Map, java.lang.Class)}
1436 */
1437 @Deprecated
1438 public static <T> T deserializeStruct(Map<String, String> hash, Class<T> klass) {
1439 return StructUtils.deserializeStruct(hash, klass);
1440 }
1441
1442 /**
1443 * Gets a map of all settings that are currently stored
1444 * @return The settings
1445 */
1446 public Map<String, Setting<?>> getAllSettings() {
1447 return new TreeMap<>(settingsMap);
1448 }
1449
1450 /**
1451 * Gets a map of all currently known defaults
1452 * @return The map (key/setting)
1453 */
1454 public Map<String, Setting<?>> getAllDefaults() {
1455 return new TreeMap<>(defaultsMap);
1456 }
1457
1458 /**
1459 * Updates system properties with the current values in the preferences.
1460 */
1461 public void updateSystemProperties() {
1462 if ("true".equals(get("prefer.ipv6", "auto")) && !"true".equals(Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"))) {
1463 // never set this to false, only true!
1464 Logging.info(tr("Try enabling IPv6 network, prefering IPv6 over IPv4 (only works on early startup)."));
1465 }
1466 Utils.updateSystemProperty("http.agent", Version.getInstance().getAgentString());
1467 Utils.updateSystemProperty("user.language", get("language"));
1468 // Workaround to fix a Java bug. This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739
1469 // Force AWT toolkit to update its internal preferences (fix #6345).
1470 // Does not work anymore with Java 9, to remove with Java 9 migration
1471 if (Utils.getJavaVersion() < 9 && !GraphicsEnvironment.isHeadless()) {
1472 try {
1473 Field field = Toolkit.class.getDeclaredField("resources");
1474 Utils.setObjectsAccessible(field);
1475 field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt"));
1476 } catch (ReflectiveOperationException | RuntimeException e) { // NOPMD
1477 // Catch RuntimeException in order to catch InaccessibleObjectException, new in Java 9
1478 Logging.warn(e);
1479 }
1480 }
1481 // Possibility to disable SNI (not by default) in case of misconfigured https servers
1482 // See #9875 + http://stackoverflow.com/a/14884941/2257172
1483 // then https://josm.openstreetmap.de/ticket/12152#comment:5 for details
1484 if (getBoolean("jdk.tls.disableSNIExtension", false)) {
1485 Utils.updateSystemProperty("jsse.enableSNIExtension", "false");
1486 }
1487 }
1488
1489 /**
1490 * Replies the collection of plugin site URLs from where plugin lists can be downloaded.
1491 * @return the collection of plugin site URLs
1492 * @see #getOnlinePluginSites
1493 */
1494 public Collection<String> getPluginSites() {
1495 return getList("pluginmanager.sites", Collections.singletonList(Main.getJOSMWebsite()+"/pluginicons%<?plugins=>"));
1496 }
1497
1498 /**
1499 * Returns the list of plugin sites available according to offline mode settings.
1500 * @return the list of available plugin sites
1501 * @since 8471
1502 */
1503 public Collection<String> getOnlinePluginSites() {
1504 Collection<String> pluginSites = new ArrayList<>(getPluginSites());
1505 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
1506 try {
1507 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
1508 } catch (OfflineAccessException ex) {
1509 Logging.log(Logging.LEVEL_WARN, ex);
1510 it.remove();
1511 }
1512 }
1513 return pluginSites;
1514 }
1515
1516 /**
1517 * Sets the collection of plugin site URLs.
1518 *
1519 * @param sites the site URLs
1520 */
1521 public void setPluginSites(Collection<String> sites) {
1522 putList("pluginmanager.sites", new ArrayList<>(sites));
1523 }
1524
1525 /**
1526 * Returns XML describing these preferences.
1527 * @param nopass if password must be excluded
1528 * @return XML
1529 */
1530 public String toXML(boolean nopass) {
1531 return toXML(settingsMap.entrySet(), nopass, false);
1532 }
1533
1534 /**
1535 * Returns XML describing the given preferences.
1536 * @param settings preferences settings
1537 * @param nopass if password must be excluded
1538 * @param defaults true, if default values are converted to XML, false for
1539 * regular preferences
1540 * @return XML
1541 */
1542 public String toXML(Collection<Entry<String, Setting<?>>> settings, boolean nopass, boolean defaults) {
1543 try (
1544 StringWriter sw = new StringWriter();
1545 PreferencesWriter prefWriter = new PreferencesWriter(new PrintWriter(sw), nopass, defaults)
1546 ) {
1547 prefWriter.write(settings);
1548 sw.flush();
1549 return sw.toString();
1550 } catch (IOException e) {
1551 Logging.error(e);
1552 return null;
1553 }
1554 }
1555
1556 /**
1557 * Removes obsolete preference settings. If you throw out a once-used preference
1558 * setting, add it to the list here with an expiry date (written as comment). If you
1559 * see something with an expiry date in the past, remove it from the list.
1560 * @param loadedVersion JOSM version when the preferences file was written
1561 */
1562 private void removeObsolete(int loadedVersion) {
1563 // drop in March 2017
1564 removeUrlFromEntries(loadedVersion, 10063,
1565 "validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.entries",
1566 "resource://data/validator/power.mapcss");
1567 // drop in March 2017
1568 if (loadedVersion < 11058) {
1569 migrateOldColorKeys();
1570 }
1571 // drop in September 2017
1572 if (loadedVersion < 11424) {
1573 addNewerDefaultEntry(
1574 "validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.entries",
1575 "resource://data/validator/territories.mapcss");
1576 }
1577
1578 for (String key : OBSOLETE_PREF_KEYS) {
1579 if (settingsMap.containsKey(key)) {
1580 settingsMap.remove(key);
1581 Logging.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
1582 }
1583 }
1584 }
1585
1586 private void migrateOldColorKeys() {
1587 settingsMap.keySet().stream()
1588 .filter(key -> key.startsWith(COLOR_PREFIX))
1589 .flatMap(this::searchOldColorKey)
1590 .collect(Collectors.toList()) // to avoid ConcurrentModificationException
1591 .forEach(entry -> {
1592 final String oldKey = entry.getKey();
1593 final String newKey = entry.getValue();
1594 Logging.info("Migrating old color key {0} => {1}", oldKey, newKey);
1595 put(newKey, get(oldKey));
1596 put(oldKey, null);
1597 });
1598 }
1599
1600 private Stream<AbstractMap.SimpleImmutableEntry<String, String>> searchOldColorKey(String key) {
1601 final String newKey = ColorProperty.getColorKey(key.substring(COLOR_PREFIX.length()));
1602 return key.equals(newKey) || settingsMap.containsKey(newKey)
1603 ? Stream.empty()
1604 : Stream.of(new AbstractMap.SimpleImmutableEntry<>(key, newKey));
1605 }
1606
1607 private void removeUrlFromEntries(int loadedVersion, int versionMax, String key, String urlPart) {
1608 if (loadedVersion < versionMax) {
1609 Setting<?> setting = settingsMap.get(key);
1610 if (setting instanceof MapListSetting) {
1611 List<Map<String, String>> l = new LinkedList<>();
1612 boolean modified = false;
1613 for (Map<String, String> map: ((MapListSetting) setting).getValue()) {
1614 String url = map.get("url");
1615 if (url != null && url.contains(urlPart)) {
1616 modified = true;
1617 } else {
1618 l.add(map);
1619 }
1620 }
1621 if (modified) {
1622 putListOfMaps(key, l);
1623 }
1624 }
1625 }
1626 }
1627
1628 private void addNewerDefaultEntry(String key, final String url) {
1629 Setting<?> setting = settingsMap.get(key);
1630 if (setting instanceof MapListSetting) {
1631 List<Map<String, String>> l = new ArrayList<>(((MapListSetting) setting).getValue());
1632 if (l.stream().noneMatch(x -> x.containsValue(url))) {
1633 ValidatorPrefHelper helper = ValidatorPrefHelper.INSTANCE;
1634 Optional<ExtendedSourceEntry> val = helper.getDefault().stream().filter(x -> url.equals(x.url)).findFirst();
1635 if (val.isPresent()) {
1636 l.add(helper.serialize(val.get()));
1637 }
1638 putListOfMaps(key, l);
1639 }
1640 }
1641 }
1642
1643 /**
1644 * Enables or not the preferences file auto-save mechanism (save each time a setting is changed).
1645 * This behaviour is enabled by default.
1646 * @param enable if {@code true}, makes JOSM save preferences file each time a setting is changed
1647 * @since 7085
1648 */
1649 public final void enableSaveOnPut(boolean enable) {
1650 synchronized (this) {
1651 saveOnPut = enable;
1652 }
1653 }
1654}
Note: See TracBrowser for help on using the repository browser.