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

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

see #15229 - add separate interface IBaseDirectories to look up pref, user data and cache dir

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