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

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

see #15410 - better display of mappaint colors

  • Property svn:eol-style set to native
File size: 64.5 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 StringSetting d = (StringSetting) e.getValue();
645 if (d.getValue() != null) {
646 all.put(e.getKey().substring(6), d.getValue());
647 }
648 }
649 }
650 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
651 if (e.getKey().startsWith(COLOR_PREFIX) && (e.getValue() instanceof StringSetting)) {
652 all.put(e.getKey().substring(6), ((StringSetting) e.getValue()).getValue());
653 }
654 }
655 return all;
656 }
657
658 /**
659 * Gets an boolean that may be specialized
660 * @param key The basic key
661 * @param specName The sub-key to append to the key
662 * @param def The default value
663 * @return The boolean value or the default value if it could not be parsed
664 * @deprecated use {@link PreferencesUtils#getBoolean(IPreferences, String, String, boolean)}
665 */
666 @Deprecated
667 public synchronized boolean getBoolean(final String key, final String specName, final boolean def) {
668 boolean generic = getBoolean(key, def);
669 String skey = key+'.'+specName;
670 Setting<?> prop = settingsMap.get(skey);
671 if (prop instanceof StringSetting)
672 return Boolean.parseBoolean(((StringSetting) prop).getValue());
673 else
674 return generic;
675 }
676
677 /**
678 * Set a boolean value for a certain setting.
679 * @param key the unique identifier for the setting
680 * @param value The new value
681 * @return {@code true}, if something has changed (i.e. value is different than before)
682 * @see BooleanProperty
683 * @deprecated use {@link IPreferences#putBoolean(String, boolean)}
684 */
685 @Deprecated
686 public boolean put(final String key, final boolean value) {
687 return put(key, Boolean.toString(value));
688 }
689
690 /**
691 * Set a boolean value for a certain setting.
692 * @param key the unique identifier for the setting
693 * @param value The new value
694 * @return {@code true}, if something has changed (i.e. value is different than before)
695 * @see IntegerProperty#put(Integer)
696 * @deprecated use {@link IPreferences#putInt(String, int)}
697 */
698 @Deprecated
699 public boolean putInteger(final String key, final Integer value) {
700 return put(key, Integer.toString(value));
701 }
702
703 /**
704 * Set a boolean value for a certain setting.
705 * @param key the unique identifier for the setting
706 * @param value The new value
707 * @return {@code true}, if something has changed (i.e. value is different than before)
708 * @see DoubleProperty#put(Double)
709 * @deprecated use {@link IPreferences#putDouble(java.lang.String, double)}
710 */
711 @Deprecated
712 public boolean putDouble(final String key, final Double value) {
713 return put(key, Double.toString(value));
714 }
715
716 /**
717 * Set a boolean value for a certain setting.
718 * @param key the unique identifier for the setting
719 * @param value The new value
720 * @return {@code true}, if something has changed (i.e. value is different than before)
721 * @see LongProperty#put(Long)
722 * @deprecated use {@link IPreferences#putLong(java.lang.String, long)}
723 */
724 @Deprecated
725 public boolean putLong(final String key, final Long value) {
726 return put(key, Long.toString(value));
727 }
728
729 /**
730 * Called after every put. In case of a problem, do nothing but output the error in log.
731 * @throws IOException if any I/O error occurs
732 */
733 public synchronized void save() throws IOException {
734 save(getPreferenceFile(), settingsMap.entrySet().stream().filter(NO_DEFAULT_SETTINGS_ENTRY), false);
735 }
736
737 /**
738 * Stores the defaults to the defaults file
739 * @throws IOException If the file could not be saved
740 */
741 public synchronized void saveDefaults() throws IOException {
742 save(getDefaultsCacheFile(), defaultsMap.entrySet().stream(), true);
743 }
744
745 protected void save(File prefFile, Stream<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
746 if (!defaults) {
747 /* currently unused, but may help to fix configuration issues in future */
748 putInt("josm.version", Version.getInstance().getVersion());
749
750 updateSystemProperties();
751 }
752
753 File backupFile = new File(prefFile + "_backup");
754
755 // Backup old preferences if there are old preferences
756 if (initSuccessful && prefFile.exists() && prefFile.length() > 0) {
757 Utils.copyFile(prefFile, backupFile);
758 }
759
760 try (PreferencesWriter writer = new PreferencesWriter(
761 new PrintWriter(new File(prefFile + "_tmp"), StandardCharsets.UTF_8.name()), false, defaults)) {
762 writer.write(settings);
763 }
764
765 File tmpFile = new File(prefFile + "_tmp");
766 Utils.copyFile(tmpFile, prefFile);
767 Utils.deleteFile(tmpFile, marktr("Unable to delete temporary file {0}"));
768
769 setCorrectPermissions(prefFile);
770 setCorrectPermissions(backupFile);
771 }
772
773 private static void setCorrectPermissions(File file) {
774 if (!file.setReadable(false, false) && Logging.isTraceEnabled()) {
775 Logging.trace(tr("Unable to set file non-readable {0}", file.getAbsolutePath()));
776 }
777 if (!file.setWritable(false, false) && Logging.isTraceEnabled()) {
778 Logging.trace(tr("Unable to set file non-writable {0}", file.getAbsolutePath()));
779 }
780 if (!file.setExecutable(false, false) && Logging.isTraceEnabled()) {
781 Logging.trace(tr("Unable to set file non-executable {0}", file.getAbsolutePath()));
782 }
783 if (!file.setReadable(true, true) && Logging.isTraceEnabled()) {
784 Logging.trace(tr("Unable to set file readable {0}", file.getAbsolutePath()));
785 }
786 if (!file.setWritable(true, true) && Logging.isTraceEnabled()) {
787 Logging.trace(tr("Unable to set file writable {0}", file.getAbsolutePath()));
788 }
789 }
790
791 /**
792 * Loads preferences from settings file.
793 * @throws IOException if any I/O error occurs while reading the file
794 * @throws SAXException if the settings file does not contain valid XML
795 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
796 */
797 protected void load() throws IOException, SAXException, XMLStreamException {
798 File pref = getPreferenceFile();
799 PreferencesReader.validateXML(pref);
800 PreferencesReader reader = new PreferencesReader(pref, false);
801 reader.parse();
802 settingsMap.clear();
803 settingsMap.putAll(reader.getSettings());
804 updateSystemProperties();
805 removeObsolete(reader.getVersion());
806 }
807
808 /**
809 * Loads default preferences from default settings cache file.
810 *
811 * Discards entries older than {@link #MAX_AGE_DEFAULT_PREFERENCES}.
812 *
813 * @throws IOException if any I/O error occurs while reading the file
814 * @throws SAXException if the settings file does not contain valid XML
815 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
816 */
817 protected void loadDefaults() throws IOException, XMLStreamException, SAXException {
818 File def = getDefaultsCacheFile();
819 PreferencesReader.validateXML(def);
820 PreferencesReader reader = new PreferencesReader(def, true);
821 reader.parse();
822 defaultsMap.clear();
823 long minTime = System.currentTimeMillis() / 1000 - MAX_AGE_DEFAULT_PREFERENCES;
824 for (Entry<String, Setting<?>> e : reader.getSettings().entrySet()) {
825 if (e.getValue().getTime() >= minTime) {
826 defaultsMap.put(e.getKey(), e.getValue());
827 }
828 }
829 }
830
831 /**
832 * Loads preferences from XML reader.
833 * @param in XML reader
834 * @throws XMLStreamException if any XML stream error occurs
835 * @throws IOException if any I/O error occurs
836 */
837 public void fromXML(Reader in) throws XMLStreamException, IOException {
838 PreferencesReader reader = new PreferencesReader(in, false);
839 reader.parse();
840 settingsMap.clear();
841 settingsMap.putAll(reader.getSettings());
842 }
843
844 /**
845 * Initializes preferences.
846 * @param reset if {@code true}, current settings file is replaced by the default one
847 */
848 public void init(boolean reset) {
849 initSuccessful = false;
850 // get the preferences.
851 File prefDir = getPreferencesDirectory(false);
852 if (prefDir.exists()) {
853 if (!prefDir.isDirectory()) {
854 Logging.warn(tr("Failed to initialize preferences. Preference directory ''{0}'' is not a directory.",
855 prefDir.getAbsoluteFile()));
856 JOptionPane.showMessageDialog(
857 Main.parent,
858 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>",
859 prefDir.getAbsoluteFile()),
860 tr("Error"),
861 JOptionPane.ERROR_MESSAGE
862 );
863 return;
864 }
865 } else {
866 if (!prefDir.mkdirs()) {
867 Logging.warn(tr("Failed to initialize preferences. Failed to create missing preference directory: {0}",
868 prefDir.getAbsoluteFile()));
869 JOptionPane.showMessageDialog(
870 Main.parent,
871 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",
872 prefDir.getAbsoluteFile()),
873 tr("Error"),
874 JOptionPane.ERROR_MESSAGE
875 );
876 return;
877 }
878 }
879
880 File preferenceFile = getPreferenceFile();
881 try {
882 if (!preferenceFile.exists()) {
883 Logging.info(tr("Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
884 resetToDefault();
885 save();
886 } else if (reset) {
887 File backupFile = new File(prefDir, "preferences.xml.bak");
888 Main.platform.rename(preferenceFile, backupFile);
889 Logging.warn(tr("Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
890 resetToDefault();
891 save();
892 }
893 } catch (IOException e) {
894 Logging.error(e);
895 JOptionPane.showMessageDialog(
896 Main.parent,
897 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",
898 getPreferenceFile().getAbsoluteFile()),
899 tr("Error"),
900 JOptionPane.ERROR_MESSAGE
901 );
902 return;
903 }
904 try {
905 load();
906 initSuccessful = true;
907 } catch (IOException | SAXException | XMLStreamException e) {
908 Logging.error(e);
909 File backupFile = new File(prefDir, "preferences.xml.bak");
910 JOptionPane.showMessageDialog(
911 Main.parent,
912 tr("<html>Preferences file had errors.<br> Making backup of old one to <br>{0}<br> " +
913 "and creating a new default preference file.</html>",
914 backupFile.getAbsoluteFile()),
915 tr("Error"),
916 JOptionPane.ERROR_MESSAGE
917 );
918 Main.platform.rename(preferenceFile, backupFile);
919 try {
920 resetToDefault();
921 save();
922 } catch (IOException e1) {
923 Logging.error(e1);
924 Logging.warn(tr("Failed to initialize preferences. Failed to reset preference file to default: {0}", getPreferenceFile()));
925 }
926 }
927 File def = getDefaultsCacheFile();
928 if (def.exists()) {
929 try {
930 loadDefaults();
931 } catch (IOException | XMLStreamException | SAXException e) {
932 Logging.error(e);
933 Logging.warn(tr("Failed to load defaults cache file: {0}", def));
934 defaultsMap.clear();
935 if (!def.delete()) {
936 Logging.warn(tr("Failed to delete faulty defaults cache file: {0}", def));
937 }
938 }
939 }
940 }
941
942 /**
943 * Resets the preferences to their initial state. This resets all values and file associations.
944 * The default values and listeners are not removed.
945 * <p>
946 * It is meant to be called before {@link #init(boolean)}
947 * @since 10876
948 */
949 public void resetToInitialState() {
950 resetToDefault();
951 preferencesDir = null;
952 cacheDir = null;
953 userdataDir = null;
954 saveOnPut = true;
955 initSuccessful = false;
956 }
957
958 /**
959 * Reset all values stored in this map to the default values. This clears the preferences.
960 */
961 public final void resetToDefault() {
962 settingsMap.clear();
963 }
964
965 /**
966 * Convenience method for accessing colour preferences.
967 * <p>
968 * To be removed: end of 2016
969 *
970 * @param colName name of the colour
971 * @param def default value
972 * @return a Color object for the configured colour, or the default value if none configured.
973 * @deprecated Use a {@link ColorProperty} instead.
974 */
975 @Deprecated
976 public synchronized Color getColor(String colName, Color def) {
977 return getColor(colName, null, def);
978 }
979
980 /* only for preferences */
981 public synchronized String getColorName(String o) {
982 Matcher m = COLOR_LAYER_PATTERN.matcher(o);
983 if (m.matches()) {
984 return tr("Layer: {0}", tr(I18n.escape(m.group(1))));
985 }
986 String fullKey = COLOR_PREFIX + o;
987 if (colornames.containsKey(fullKey)) {
988 String name = colornames.get(fullKey);
989 Matcher m2 = COLOR_MAPPAINT_PATTERN.matcher(name);
990 if (m2.matches()) {
991 return tr("Paint style {0}: {1}", tr(I18n.escape(m2.group(1))), tr(I18n.escape(m2.group(2))));
992 } else {
993 return tr(I18n.escape(colornames.get(fullKey)));
994 }
995 } else {
996 return fullKey;
997 }
998 }
999
1000 /**
1001 * Convenience method for accessing colour preferences.
1002 * <p>
1003 * To be removed: end of 2016
1004 * @param colName name of the colour
1005 * @param specName name of the special colour settings
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 ColorProperty} instead.
1009 * You can replace this by: <code>new ColorProperty(colName, def).getChildColor(specName)</code>
1010 */
1011 @Deprecated
1012 public synchronized Color getColor(String colName, String specName, Color def) {
1013 String colKey = ColorProperty.getColorKey(colName);
1014 registerColor(colKey, colName);
1015 String colStr = specName != null ? get(COLOR_PREFIX+specName) : "";
1016 if (colStr.isEmpty()) {
1017 colStr = get(colKey, ColorHelper.color2html(def, true));
1018 }
1019 if (colStr != null && !colStr.isEmpty()) {
1020 return ColorHelper.html2color(colStr);
1021 } else {
1022 return def;
1023 }
1024 }
1025
1026 /**
1027 * Registers a color name conversion for the global color registry.
1028 * @param colKey The key
1029 * @param colName The name of the color.
1030 * @since 10824
1031 */
1032 public void registerColor(String colKey, String colName) {
1033 if (!colKey.equals(colName)) {
1034 colornames.put(colKey, colName);
1035 }
1036 }
1037
1038 /**
1039 * Gets the default color that was registered with the preference
1040 * @param colKey The color name
1041 * @return The color
1042 */
1043 public synchronized Color getDefaultColor(String colKey) {
1044 StringSetting col = Utils.cast(defaultsMap.get(COLOR_PREFIX+colKey), StringSetting.class);
1045 String colStr = col == null ? null : col.getValue();
1046 return colStr == null || colStr.isEmpty() ? null : ColorHelper.html2color(colStr);
1047 }
1048
1049 /**
1050 * Stores a color
1051 * @param colKey The color name
1052 * @param val The color
1053 * @return true if the setting was modified
1054 * @see ColorProperty#put(Color)
1055 */
1056 public synchronized boolean putColor(String colKey, Color val) {
1057 return put(COLOR_PREFIX+colKey, val != null ? ColorHelper.color2html(val, true) : null);
1058 }
1059
1060 /**
1061 * Gets an integer preference
1062 * @param key The preference key
1063 * @param def The default value to use
1064 * @return The integer
1065 * @see IntegerProperty#get()
1066 * @deprecated use {@link IPreferences#getInt(String, int)}
1067 */
1068 @Deprecated
1069 public synchronized int getInteger(String key, int def) {
1070 String v = get(key, Integer.toString(def));
1071 if (v.isEmpty())
1072 return def;
1073
1074 try {
1075 return Integer.parseInt(v);
1076 } catch (NumberFormatException e) {
1077 // fall out
1078 Logging.trace(e);
1079 }
1080 return def;
1081 }
1082
1083 /**
1084 * Gets an integer that may be specialized
1085 * @param key The basic key
1086 * @param specName The sub-key to append to the key
1087 * @param def The default value
1088 * @return The integer value or the default value if it could not be parsed
1089 * @deprecated use {@link PreferencesUtils#getInteger(IPreferences, String, String, int)}
1090 */
1091 @Deprecated
1092 public synchronized int getInteger(String key, String specName, int def) {
1093 String v = get(key+'.'+specName);
1094 if (v.isEmpty())
1095 v = get(key, Integer.toString(def));
1096 if (v.isEmpty())
1097 return def;
1098
1099 try {
1100 return Integer.parseInt(v);
1101 } catch (NumberFormatException e) {
1102 // fall out
1103 Logging.trace(e);
1104 }
1105 return def;
1106 }
1107
1108 /**
1109 * Get a list of values for a certain key
1110 * @param key the identifier for the setting
1111 * @param def the default value.
1112 * @return the corresponding value if the property has been set before, {@code def} otherwise
1113 * @deprecated use {@link IPreferences#getList(java.lang.String, java.util.List)}
1114 */
1115 @Deprecated
1116 public Collection<String> getCollection(String key, Collection<String> def) {
1117 return getSetting(key, ListSetting.create(def), ListSetting.class).getValue();
1118 }
1119
1120 /**
1121 * Get a list of values for a certain key
1122 * @param key the identifier for the setting
1123 * @return the corresponding value if the property has been set before, an empty collection otherwise.
1124 * @deprecated use {@link IPreferences#getList(java.lang.String)}
1125 */
1126 @Deprecated
1127 public Collection<String> getCollection(String key) {
1128 Collection<String> val = getList(key, null);
1129 return val == null ? Collections.<String>emptyList() : val;
1130 }
1131
1132 /**
1133 * Removes a value from a given String collection
1134 * @param key The preference key the collection is stored with
1135 * @param value The value that should be removed in the collection
1136 * @see #getList(String)
1137 * @deprecated use {@link PreferencesUtils#removeFromList(IPreferences, String, String)}
1138 */
1139 @Deprecated
1140 public synchronized void removeFromCollection(String key, String value) {
1141 List<String> a = new ArrayList<>(getList(key, Collections.<String>emptyList()));
1142 a.remove(value);
1143 putList(key, a);
1144 }
1145
1146 /**
1147 * Set a value for a certain setting. The changed setting is saved to the preference file immediately.
1148 * Due to caching mechanisms on modern operating systems and hardware, this shouldn't be a performance problem.
1149 * @param key the unique identifier for the setting
1150 * @param setting the value of the setting. In case it is null, the key-value entry will be removed.
1151 * @return {@code true}, if something has changed (i.e. value is different than before)
1152 */
1153 @Override
1154 public boolean putSetting(final String key, Setting<?> setting) {
1155 CheckParameterUtil.ensureParameterNotNull(key);
1156 if (setting != null && setting.getValue() == null)
1157 throw new IllegalArgumentException("setting argument must not have null value");
1158 Setting<?> settingOld;
1159 Setting<?> settingCopy = null;
1160 synchronized (this) {
1161 if (setting == null) {
1162 settingOld = settingsMap.remove(key);
1163 if (settingOld == null)
1164 return false;
1165 } else {
1166 settingOld = settingsMap.get(key);
1167 if (setting.equals(settingOld))
1168 return false;
1169 if (settingOld == null && setting.equals(defaultsMap.get(key)))
1170 return false;
1171 settingCopy = setting.copy();
1172 settingsMap.put(key, settingCopy);
1173 }
1174 if (saveOnPut) {
1175 try {
1176 save();
1177 } catch (IOException e) {
1178 Logging.log(Logging.LEVEL_WARN, tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()), e);
1179 }
1180 }
1181 }
1182 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
1183 firePreferenceChanged(key, settingOld, settingCopy);
1184 return true;
1185 }
1186
1187 /**
1188 * Get a setting of any type
1189 * @param key The key for the setting
1190 * @param def The default value to use if it was not found
1191 * @return The setting
1192 */
1193 public synchronized Setting<?> getSetting(String key, Setting<?> def) {
1194 return getSetting(key, def, Setting.class);
1195 }
1196
1197 /**
1198 * Get settings value for a certain key and provide default a value.
1199 * @param <T> the setting type
1200 * @param key the identifier for the setting
1201 * @param def the default value. For each call of getSetting() with a given key, the default value must be the same.
1202 * <code>def</code> must not be null, but the value of <code>def</code> can be null.
1203 * @param klass the setting type (same as T)
1204 * @return the corresponding value if the property has been set before, {@code def} otherwise
1205 */
1206 @SuppressWarnings("unchecked")
1207 @Override
1208 public synchronized <T extends Setting<?>> T getSetting(String key, T def, Class<T> klass) {
1209 CheckParameterUtil.ensureParameterNotNull(key);
1210 CheckParameterUtil.ensureParameterNotNull(def);
1211 Setting<?> oldDef = defaultsMap.get(key);
1212 if (oldDef != null && oldDef.isNew() && oldDef.getValue() != null && def.getValue() != null && !def.equals(oldDef)) {
1213 Logging.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
1214 }
1215 if (def.getValue() != null || oldDef == null) {
1216 Setting<?> defCopy = def.copy();
1217 defCopy.setTime(System.currentTimeMillis() / 1000);
1218 defCopy.setNew(true);
1219 defaultsMap.put(key, defCopy);
1220 }
1221 Setting<?> prop = settingsMap.get(key);
1222 if (klass.isInstance(prop)) {
1223 return (T) prop;
1224 } else {
1225 return def;
1226 }
1227 }
1228
1229 /**
1230 * Put a collection.
1231 * @param key key
1232 * @param value value
1233 * @return {@code true}, if something has changed (i.e. value is different than before)
1234 * @deprecated use {@link IPreferences#putList(java.lang.String, java.util.List)}
1235 */
1236 @Deprecated
1237 public boolean putCollection(String key, Collection<String> value) {
1238 return putSetting(key, value == null ? null : ListSetting.create(value));
1239 }
1240
1241 /**
1242 * Saves at most {@code maxsize} items of collection {@code val}.
1243 * @param key key
1244 * @param maxsize max number of items to save
1245 * @param val value
1246 * @return {@code true}, if something has changed (i.e. value is different than before)
1247 * @deprecated use {@link PreferencesUtils#putListBounded(IPreferences, String, int, List)}
1248 */
1249 @Deprecated
1250 public boolean putCollectionBounded(String key, int maxsize, Collection<String> val) {
1251 List<String> newCollection = new ArrayList<>(Math.min(maxsize, val.size()));
1252 for (String i : val) {
1253 if (newCollection.size() >= maxsize) {
1254 break;
1255 }
1256 newCollection.add(i);
1257 }
1258 return putList(key, newCollection);
1259 }
1260
1261 /**
1262 * Used to read a 2-dimensional array of strings from the preference file.
1263 * If not a single entry could be found, <code>def</code> is returned.
1264 * @param key preference key
1265 * @param def default array value
1266 * @return array value
1267 * @deprecated use {@link #getListOfLists(java.lang.String, java.util.List)}
1268 */
1269 @Deprecated
1270 @SuppressWarnings({ "unchecked", "rawtypes" })
1271 public synchronized Collection<Collection<String>> getArray(String key, Collection<Collection<String>> def) {
1272 ListListSetting val = getSetting(key, ListListSetting.create(def), ListListSetting.class);
1273 return (Collection) val.getValue();
1274 }
1275
1276 /**
1277 * Gets a collection of string collections for the given key
1278 * @param key The key
1279 * @return The collection of string collections or an empty collection as default
1280 * @deprecated use {@link IPreferences#getListOfLists(java.lang.String)}
1281 */
1282 @Deprecated
1283 public Collection<Collection<String>> getArray(String key) {
1284 Collection<Collection<String>> res = getArray(key, null);
1285 return res == null ? Collections.<Collection<String>>emptyList() : res;
1286 }
1287
1288 /**
1289 * Put an array.
1290 * @param key key
1291 * @param value value
1292 * @return {@code true}, if something has changed (i.e. value is different than before)
1293 * @deprecated use {@link IPreferences#putListOfLists(java.lang.String, java.util.List)}
1294 */
1295 @Deprecated
1296 public boolean putArray(String key, Collection<Collection<String>> value) {
1297 return putSetting(key, value == null ? null : ListListSetting.create(value));
1298 }
1299
1300 /**
1301 * Gets a collection of key/value maps.
1302 * @param key The key to search at
1303 * @param def The default value to use
1304 * @return The stored value or the default one if it could not be parsed
1305 * @deprecated use {@link IPreferences#getListOfMaps(java.lang.String, java.util.List)}
1306 */
1307 @Deprecated
1308 public Collection<Map<String, String>> getListOfStructs(String key, Collection<Map<String, String>> def) {
1309 return getSetting(key, new MapListSetting(def == null ? null : new ArrayList<>(def)), MapListSetting.class).getValue();
1310 }
1311
1312 /**
1313 * Stores a list of structs
1314 * @param key The key to store the list in
1315 * @param value A list of key/value maps
1316 * @return <code>true</code> if the value was changed
1317 * @see #getListOfMaps(java.lang.String, java.util.List)
1318 * @deprecated use {@link IPreferences#putListOfMaps(java.lang.String, java.util.List)}
1319 */
1320 @Deprecated
1321 public boolean putListOfStructs(String key, Collection<Map<String, String>> value) {
1322 return putSetting(key, value == null ? null : new MapListSetting(new ArrayList<>(value)));
1323 }
1324
1325 /**
1326 * Annotation used for converting objects to String Maps and vice versa.
1327 * Indicates that a certain field should be considered in the conversion process. Otherwise it is ignored.
1328 *
1329 * @see #serializeStruct(java.lang.Object, java.lang.Class)
1330 * @see #deserializeStruct(java.util.Map, java.lang.Class)
1331 * @deprecated use {@link StructUtils.StructEntry}
1332 */
1333 @Deprecated
1334 @Retention(RetentionPolicy.RUNTIME) // keep annotation at runtime
1335 public @interface pref { }
1336
1337 /**
1338 * Annotation used for converting objects to String Maps.
1339 * Indicates that a certain field should be written to the map, even if the value is the same as the default value.
1340 *
1341 * @see #serializeStruct(java.lang.Object, java.lang.Class)
1342 * @deprecated use {@link StructUtils.WriteExplicitly}
1343 */
1344 @Deprecated
1345 @Retention(RetentionPolicy.RUNTIME) // keep annotation at runtime
1346 public @interface writeExplicitly { }
1347
1348 /**
1349 * Get a list of hashes which are represented by a struct-like class.
1350 * Possible properties are given by fields of the class klass that have the @pref annotation.
1351 * Default constructor is used to initialize the struct objects, properties then override some of these default values.
1352 * @param <T> klass type
1353 * @param key main preference key
1354 * @param klass The struct class
1355 * @return a list of objects of type T or an empty list if nothing was found
1356 * @deprecated use {@link StructUtils#getListOfStructs(IPreferences, String, Class)}
1357 */
1358 @Deprecated
1359 public <T> List<T> getListOfStructs(String key, Class<T> klass) {
1360 return StructUtils.getListOfStructs(this, key, klass);
1361 }
1362
1363 /**
1364 * same as above, but returns def if nothing was found
1365 * @param <T> klass type
1366 * @param key main preference key
1367 * @param def default value
1368 * @param klass The struct class
1369 * @return a list of objects of type T or {@code def} if nothing was found
1370 * @deprecated use {@link StructUtils#getListOfStructs(IPreferences, String, Collection, Class)}
1371 */
1372 @Deprecated
1373 public <T> List<T> getListOfStructs(String key, Collection<T> def, Class<T> klass) {
1374 return StructUtils.getListOfStructs(this, key, def, klass);
1375 }
1376
1377 /**
1378 * Convenience method that saves a MapListSetting which is provided as a collection of objects.
1379 *
1380 * Each object is converted to a <code>Map&lt;String, String&gt;</code> using the fields with {@link pref} annotation.
1381 * The field name is the key and the value will be converted to a string.
1382 *
1383 * Considers only fields that have the @pref annotation.
1384 * In addition it does not write fields with null values. (Thus they are cleared)
1385 * Default values are given by the field values after default constructor has been called.
1386 * Fields equal to the default value are not written unless the field has the @writeExplicitly annotation.
1387 * @param <T> the class,
1388 * @param key main preference key
1389 * @param val the list that is supposed to be saved
1390 * @param klass The struct class
1391 * @return true if something has changed
1392 * @deprecated use {@link StructUtils#putListOfStructs(IPreferences, String, Collection, Class)}
1393 */
1394 @Deprecated
1395 public <T> boolean putListOfStructs(String key, Collection<T> val, Class<T> klass) {
1396 return StructUtils.putListOfStructs(this, key, val, klass);
1397 }
1398
1399 /**
1400 * Convert an object to a String Map, by using field names and values as map key and value.
1401 *
1402 * The field value is converted to a String.
1403 *
1404 * Only fields with annotation {@link pref} are taken into account.
1405 *
1406 * Fields will not be written to the map if the value is null or unchanged
1407 * (compared to an object created with the no-arg-constructor).
1408 * The {@link writeExplicitly} annotation overrides this behavior, i.e. the default value will also be written.
1409 *
1410 * @param <T> the class of the object <code>struct</code>
1411 * @param struct the object to be converted
1412 * @param klass the class T
1413 * @return the resulting map (same data content as <code>struct</code>)
1414 * @deprecated use {@link StructUtils#serializeStruct(java.lang.Object, java.lang.Class)}
1415 */
1416 @Deprecated
1417 public static <T> Map<String, String> serializeStruct(T struct, Class<T> klass) {
1418 return StructUtils.serializeStruct(struct, klass);
1419 }
1420
1421 /**
1422 * Converts a String-Map to an object of a certain class, by comparing map keys to field names of the class and assigning
1423 * map values to the corresponding fields.
1424 *
1425 * The map value (a String) is converted to the field type. Supported types are: boolean, Boolean, int, Integer, double,
1426 * Double, String, Map&lt;String, String&gt; and Map&lt;String, List&lt;String&gt;&gt;.
1427 *
1428 * Only fields with annotation {@link pref} are taken into account.
1429 * @param <T> the class
1430 * @param hash the string map with initial values
1431 * @param klass the class T
1432 * @return an object of class T, initialized as described above
1433 * @deprecated use {@link StructUtils#deserializeStruct(java.util.Map, java.lang.Class)}
1434 */
1435 @Deprecated
1436 public static <T> T deserializeStruct(Map<String, String> hash, Class<T> klass) {
1437 return StructUtils.deserializeStruct(hash, klass);
1438 }
1439
1440 /**
1441 * Gets a map of all settings that are currently stored
1442 * @return The settings
1443 */
1444 public Map<String, Setting<?>> getAllSettings() {
1445 return new TreeMap<>(settingsMap);
1446 }
1447
1448 /**
1449 * Gets a map of all currently known defaults
1450 * @return The map (key/setting)
1451 */
1452 public Map<String, Setting<?>> getAllDefaults() {
1453 return new TreeMap<>(defaultsMap);
1454 }
1455
1456 /**
1457 * Updates system properties with the current values in the preferences.
1458 */
1459 public void updateSystemProperties() {
1460 if ("true".equals(get("prefer.ipv6", "auto")) && !"true".equals(Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"))) {
1461 // never set this to false, only true!
1462 Logging.info(tr("Try enabling IPv6 network, prefering IPv6 over IPv4 (only works on early startup)."));
1463 }
1464 Utils.updateSystemProperty("http.agent", Version.getInstance().getAgentString());
1465 Utils.updateSystemProperty("user.language", get("language"));
1466 // Workaround to fix a Java bug. This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739
1467 // Force AWT toolkit to update its internal preferences (fix #6345).
1468 // Does not work anymore with Java 9, to remove with Java 9 migration
1469 if (Utils.getJavaVersion() < 9 && !GraphicsEnvironment.isHeadless()) {
1470 try {
1471 Field field = Toolkit.class.getDeclaredField("resources");
1472 Utils.setObjectsAccessible(field);
1473 field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt"));
1474 } catch (ReflectiveOperationException | RuntimeException e) { // NOPMD
1475 // Catch RuntimeException in order to catch InaccessibleObjectException, new in Java 9
1476 Logging.warn(e);
1477 }
1478 }
1479 // Possibility to disable SNI (not by default) in case of misconfigured https servers
1480 // See #9875 + http://stackoverflow.com/a/14884941/2257172
1481 // then https://josm.openstreetmap.de/ticket/12152#comment:5 for details
1482 if (getBoolean("jdk.tls.disableSNIExtension", false)) {
1483 Utils.updateSystemProperty("jsse.enableSNIExtension", "false");
1484 }
1485 }
1486
1487 /**
1488 * Replies the collection of plugin site URLs from where plugin lists can be downloaded.
1489 * @return the collection of plugin site URLs
1490 * @see #getOnlinePluginSites
1491 */
1492 public Collection<String> getPluginSites() {
1493 return getList("pluginmanager.sites", Collections.singletonList(Main.getJOSMWebsite()+"/pluginicons%<?plugins=>"));
1494 }
1495
1496 /**
1497 * Returns the list of plugin sites available according to offline mode settings.
1498 * @return the list of available plugin sites
1499 * @since 8471
1500 */
1501 public Collection<String> getOnlinePluginSites() {
1502 Collection<String> pluginSites = new ArrayList<>(getPluginSites());
1503 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
1504 try {
1505 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
1506 } catch (OfflineAccessException ex) {
1507 Logging.log(Logging.LEVEL_WARN, ex);
1508 it.remove();
1509 }
1510 }
1511 return pluginSites;
1512 }
1513
1514 /**
1515 * Sets the collection of plugin site URLs.
1516 *
1517 * @param sites the site URLs
1518 */
1519 public void setPluginSites(Collection<String> sites) {
1520 putList("pluginmanager.sites", new ArrayList<>(sites));
1521 }
1522
1523 /**
1524 * Returns XML describing these preferences.
1525 * @param nopass if password must be excluded
1526 * @return XML
1527 */
1528 public String toXML(boolean nopass) {
1529 return toXML(settingsMap.entrySet(), nopass, false);
1530 }
1531
1532 /**
1533 * Returns XML describing the given preferences.
1534 * @param settings preferences settings
1535 * @param nopass if password must be excluded
1536 * @param defaults true, if default values are converted to XML, false for
1537 * regular preferences
1538 * @return XML
1539 */
1540 public String toXML(Collection<Entry<String, Setting<?>>> settings, boolean nopass, boolean defaults) {
1541 try (
1542 StringWriter sw = new StringWriter();
1543 PreferencesWriter prefWriter = new PreferencesWriter(new PrintWriter(sw), nopass, defaults)
1544 ) {
1545 prefWriter.write(settings);
1546 sw.flush();
1547 return sw.toString();
1548 } catch (IOException e) {
1549 Logging.error(e);
1550 return null;
1551 }
1552 }
1553
1554 /**
1555 * Removes obsolete preference settings. If you throw out a once-used preference
1556 * setting, add it to the list here with an expiry date (written as comment). If you
1557 * see something with an expiry date in the past, remove it from the list.
1558 * @param loadedVersion JOSM version when the preferences file was written
1559 */
1560 private void removeObsolete(int loadedVersion) {
1561 // drop in March 2017
1562 removeUrlFromEntries(loadedVersion, 10063,
1563 "validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.entries",
1564 "resource://data/validator/power.mapcss");
1565 // drop in March 2017
1566 if (loadedVersion < 11058) {
1567 migrateOldColorKeys();
1568 }
1569 // drop in September 2017
1570 if (loadedVersion < 11424) {
1571 addNewerDefaultEntry(
1572 "validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.entries",
1573 "resource://data/validator/territories.mapcss");
1574 }
1575
1576 for (String key : OBSOLETE_PREF_KEYS) {
1577 if (settingsMap.containsKey(key)) {
1578 settingsMap.remove(key);
1579 Logging.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
1580 }
1581 }
1582 }
1583
1584 private void migrateOldColorKeys() {
1585 settingsMap.keySet().stream()
1586 .filter(key -> key.startsWith(COLOR_PREFIX))
1587 .flatMap(this::searchOldColorKey)
1588 .collect(Collectors.toList()) // to avoid ConcurrentModificationException
1589 .forEach(entry -> {
1590 final String oldKey = entry.getKey();
1591 final String newKey = entry.getValue();
1592 Logging.info("Migrating old color key {0} => {1}", oldKey, newKey);
1593 put(newKey, get(oldKey));
1594 put(oldKey, null);
1595 });
1596 }
1597
1598 private Stream<AbstractMap.SimpleImmutableEntry<String, String>> searchOldColorKey(String key) {
1599 final String newKey = ColorProperty.getColorKey(key.substring(COLOR_PREFIX.length()));
1600 return key.equals(newKey) || settingsMap.containsKey(newKey)
1601 ? Stream.empty()
1602 : Stream.of(new AbstractMap.SimpleImmutableEntry<>(key, newKey));
1603 }
1604
1605 private void removeUrlFromEntries(int loadedVersion, int versionMax, String key, String urlPart) {
1606 if (loadedVersion < versionMax) {
1607 Setting<?> setting = settingsMap.get(key);
1608 if (setting instanceof MapListSetting) {
1609 List<Map<String, String>> l = new LinkedList<>();
1610 boolean modified = false;
1611 for (Map<String, String> map: ((MapListSetting) setting).getValue()) {
1612 String url = map.get("url");
1613 if (url != null && url.contains(urlPart)) {
1614 modified = true;
1615 } else {
1616 l.add(map);
1617 }
1618 }
1619 if (modified) {
1620 putListOfMaps(key, l);
1621 }
1622 }
1623 }
1624 }
1625
1626 private void addNewerDefaultEntry(String key, final String url) {
1627 Setting<?> setting = settingsMap.get(key);
1628 if (setting instanceof MapListSetting) {
1629 List<Map<String, String>> l = new ArrayList<>(((MapListSetting) setting).getValue());
1630 if (l.stream().noneMatch(x -> x.containsValue(url))) {
1631 ValidatorPrefHelper helper = ValidatorPrefHelper.INSTANCE;
1632 Optional<ExtendedSourceEntry> val = helper.getDefault().stream().filter(x -> url.equals(x.url)).findFirst();
1633 if (val.isPresent()) {
1634 l.add(helper.serialize(val.get()));
1635 }
1636 putListOfMaps(key, l);
1637 }
1638 }
1639 }
1640
1641 /**
1642 * Enables or not the preferences file auto-save mechanism (save each time a setting is changed).
1643 * This behaviour is enabled by default.
1644 * @param enable if {@code true}, makes JOSM save preferences file each time a setting is changed
1645 * @since 7085
1646 */
1647 public final void enableSaveOnPut(boolean enable) {
1648 synchronized (this) {
1649 saveOnPut = enable;
1650 }
1651 }
1652}
Note: See TracBrowser for help on using the repository browser.