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

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

see #15410 - update tests + minor fixes (2)

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