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

Last change on this file since 10223 was 10223, checked in by Don-vip, 8 years ago

findbugs: DP_DO_INSIDE_DO_PRIVILEGED + UWF_UNWRITTEN_FIELD + RC_REF_COMPARISON + OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE

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