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

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

see #15451 - fix initialization

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