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

Last change on this file since 13215 was 13173, checked in by Don-vip, 6 years ago

see #15310 - remove most of deprecated APIs

  • Property svn:eol-style set to native
File size: 38.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.io.File;
9import java.io.IOException;
10import java.io.PrintWriter;
11import java.io.Reader;
12import java.io.StringWriter;
13import java.nio.charset.StandardCharsets;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.HashMap;
18import java.util.HashSet;
19import java.util.Iterator;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23import java.util.Map.Entry;
24import java.util.Optional;
25import java.util.Set;
26import java.util.SortedMap;
27import java.util.TreeMap;
28import java.util.concurrent.TimeUnit;
29import java.util.function.Predicate;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32import java.util.stream.Stream;
33
34import javax.swing.JOptionPane;
35import javax.xml.stream.XMLStreamException;
36
37import org.openstreetmap.josm.Main;
38import org.openstreetmap.josm.data.preferences.ColorInfo;
39import org.openstreetmap.josm.data.preferences.NamedColorProperty;
40import org.openstreetmap.josm.data.preferences.PreferencesReader;
41import org.openstreetmap.josm.data.preferences.PreferencesWriter;
42import org.openstreetmap.josm.io.OfflineAccessException;
43import org.openstreetmap.josm.io.OnlineResource;
44import org.openstreetmap.josm.spi.preferences.AbstractPreferences;
45import org.openstreetmap.josm.spi.preferences.Config;
46import org.openstreetmap.josm.spi.preferences.IBaseDirectories;
47import org.openstreetmap.josm.spi.preferences.ListSetting;
48import org.openstreetmap.josm.spi.preferences.Setting;
49import org.openstreetmap.josm.spi.preferences.StringSetting;
50import org.openstreetmap.josm.tools.CheckParameterUtil;
51import org.openstreetmap.josm.tools.ColorHelper;
52import org.openstreetmap.josm.tools.I18n;
53import org.openstreetmap.josm.tools.ListenerList;
54import org.openstreetmap.josm.tools.Logging;
55import org.openstreetmap.josm.tools.Utils;
56import org.xml.sax.SAXException;
57
58/**
59 * This class holds all preferences for JOSM.
60 *
61 * Other classes can register their beloved properties here. All properties will be
62 * saved upon set-access.
63 *
64 * Each property is a key=setting pair, where key is a String and setting can be one of
65 * 4 types:
66 * string, list, list of lists and list of maps.
67 * In addition, each key has a unique default value that is set when the value is first
68 * accessed using one of the get...() methods. You can use the same preference
69 * key in different parts of the code, but the default value must be the same
70 * everywhere. A default value of null means, the setting has been requested, but
71 * no default value was set. This is used in advanced preferences to present a list
72 * off all possible settings.
73 *
74 * At the moment, you cannot put the empty string for string properties.
75 * put(key, "") means, the property is removed.
76 *
77 * @author imi
78 * @since 74
79 */
80public class Preferences extends AbstractPreferences {
81
82 private static final String COLOR_PREFIX = "color.";
83 private static final Pattern COLOR_LAYER_PATTERN = Pattern.compile("layer\\.(.+)");
84 private static final Pattern COLOR_MAPPAINT_PATTERN = Pattern.compile("mappaint\\.(.+?)\\.(.+)");
85
86 private static final String[] OBSOLETE_PREF_KEYS = {
87 "projection", /* remove entry after Nov. 2017 */
88 "projection.sub", /* remove entry after Nov. 2017 */
89 };
90
91 private static final long MAX_AGE_DEFAULT_PREFERENCES = TimeUnit.DAYS.toSeconds(50);
92
93 private final IBaseDirectories dirs;
94
95 /**
96 * Determines if preferences file is saved each time a property is changed.
97 */
98 private boolean saveOnPut = true;
99
100 /**
101 * Maps the setting name to the current value of the setting.
102 * The map must not contain null as key or value. The mapped setting objects
103 * must not have a null value.
104 */
105 protected final SortedMap<String, Setting<?>> settingsMap = new TreeMap<>();
106
107 /**
108 * Maps the setting name to the default value of the setting.
109 * The map must not contain null as key or value. The value of the mapped
110 * setting objects can be null.
111 */
112 protected final SortedMap<String, Setting<?>> defaultsMap = new TreeMap<>();
113
114 private final Predicate<Entry<String, Setting<?>>> NO_DEFAULT_SETTINGS_ENTRY =
115 e -> !e.getValue().equals(defaultsMap.get(e.getKey()));
116
117 /**
118 * Maps color keys to human readable color name
119 * @deprecated (since 12987) no longer supported
120 */
121 @Deprecated
122 protected final SortedMap<String, String> colornames = new TreeMap<>();
123
124 /**
125 * Indicates whether {@link #init(boolean)} completed successfully.
126 * Used to decide whether to write backup preference file in {@link #save()}
127 */
128 protected boolean initSuccessful;
129
130 private final ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listeners = ListenerList.create();
131
132 private final HashMap<String, ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener>> keyListeners = new HashMap<>();
133
134 /**
135 * Constructs a new {@code Preferences}.
136 */
137 public Preferences() {
138 this.dirs = Config.getDirs();
139 }
140
141 /**
142 * Constructs a new {@code Preferences}.
143 *
144 * @param dirs the directories to use for saving the preferences
145 */
146 public Preferences(IBaseDirectories dirs) {
147 this.dirs = dirs;
148 }
149
150 /**
151 * Constructs a new {@code Preferences} from an existing instance.
152 * @param pref existing preferences to copy
153 * @since 12634
154 */
155 @SuppressWarnings("deprecation")
156 public Preferences(Preferences pref) {
157 this(pref.dirs);
158 settingsMap.putAll(pref.settingsMap);
159 defaultsMap.putAll(pref.defaultsMap);
160 colornames.putAll(pref.colornames);
161 }
162
163 /**
164 * Adds a new preferences listener.
165 * @param listener The listener to add
166 * @since 12881
167 */
168 @Override
169 public void addPreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
170 if (listener != null) {
171 listeners.addListener(listener);
172 }
173 }
174
175 /**
176 * Removes a preferences listener.
177 * @param listener The listener to remove
178 * @since 12881
179 */
180 @Override
181 public void removePreferenceChangeListener(org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
182 listeners.removeListener(listener);
183 }
184
185 /**
186 * Adds a listener that only listens to changes in one preference
187 * @param key The preference key to listen to
188 * @param listener The listener to add.
189 * @since 12881
190 */
191 @Override
192 public void addKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
193 listenersForKey(key).addListener(listener);
194 }
195
196 /**
197 * Adds a weak listener that only listens to changes in one preference
198 * @param key The preference key to listen to
199 * @param listener The listener to add.
200 * @since 10824
201 */
202 public void addWeakKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
203 listenersForKey(key).addWeakListener(listener);
204 }
205
206 private ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> listenersForKey(String key) {
207 return keyListeners.computeIfAbsent(key, k -> ListenerList.create());
208 }
209
210 /**
211 * Removes a listener that only listens to changes in one preference
212 * @param key The preference key to listen to
213 * @param listener The listener to add.
214 * @since 12881
215 */
216 @Override
217 public void removeKeyPreferenceChangeListener(String key, org.openstreetmap.josm.spi.preferences.PreferenceChangedListener listener) {
218 Optional.ofNullable(keyListeners.get(key)).orElseThrow(
219 () -> new IllegalArgumentException("There are no listeners registered for " + key))
220 .removeListener(listener);
221 }
222
223 protected void firePreferenceChanged(String key, Setting<?> oldValue, Setting<?> newValue) {
224 final org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent evt =
225 new org.openstreetmap.josm.spi.preferences.DefaultPreferenceChangeEvent(key, oldValue, newValue);
226 listeners.fireEvent(listener -> listener.preferenceChanged(evt));
227
228 ListenerList<org.openstreetmap.josm.spi.preferences.PreferenceChangedListener> forKey = keyListeners.get(key);
229 if (forKey != null) {
230 forKey.fireEvent(listener -> listener.preferenceChanged(evt));
231 }
232 }
233
234 /**
235 * Get the base name of the JOSM directories for preferences, cache and user data.
236 * Default value is "JOSM", unless overridden by system property "josm.dir.name".
237 * @return the base name of the JOSM directories for preferences, cache and user data
238 */
239 public String getJOSMDirectoryBaseName() {
240 String name = System.getProperty("josm.dir.name");
241 if (name != null)
242 return name;
243 else
244 return "JOSM";
245 }
246
247 /**
248 * Get the base directories associated with this preference instance.
249 * @return the base directories
250 */
251 public IBaseDirectories getDirs() {
252 return dirs;
253 }
254
255 /**
256 * Returns the user defined preferences directory, containing the preferences.xml file
257 * @return The user defined preferences directory, containing the preferences.xml file
258 * @since 7834
259 * @deprecated use {@link #getPreferencesDirectory(boolean)}
260 */
261 @Deprecated
262 public File getPreferencesDirectory() {
263 return getPreferencesDirectory(false);
264 }
265
266 /**
267 * @param createIfMissing if true, automatically creates this directory,
268 * in case it is missing
269 * @return the preferences directory
270 * @deprecated use {@link #getDirs()} or (more generally) {@link Config#getDirs()}
271 */
272 @Deprecated
273 public File getPreferencesDirectory(boolean createIfMissing) {
274 return dirs.getPreferencesDirectory(createIfMissing);
275 }
276
277 /**
278 * Returns the user data directory, containing autosave, plugins, etc.
279 * Depending on the OS it may be the same directory as preferences directory.
280 * @return The user data directory, containing autosave, plugins, etc.
281 * @since 7834
282 * @deprecated use {@link #getUserDataDirectory(boolean)}
283 */
284 @Deprecated
285 public File getUserDataDirectory() {
286 return getUserDataDirectory(false);
287 }
288
289 /**
290 * @param createIfMissing if true, automatically creates this directory,
291 * in case it is missing
292 * @return the user data directory
293 * @deprecated use {@link #getDirs()} or (more generally) {@link Config#getDirs()}
294 */
295 @Deprecated
296 public File getUserDataDirectory(boolean createIfMissing) {
297 return dirs.getUserDataDirectory(createIfMissing);
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(dirs.getPreferencesDirectory(false), "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(dirs.getCacheDirectory(true), "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(dirs.getUserDataDirectory(false), "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 * @deprecated use {@link #getCacheDirectory(boolean)}
331 */
332 @Deprecated
333 public File getCacheDirectory() {
334 return getCacheDirectory(true);
335 }
336
337 /**
338 * @param createIfMissing if true, automatically creates this directory,
339 * in case it is missing
340 * @return the cache directory
341 * @deprecated use {@link #getDirs()} or (more generally) {@link Config#getDirs()}
342 */
343 @Deprecated
344 public File getCacheDirectory(boolean createIfMissing) {
345 return dirs.getCacheDirectory(createIfMissing);
346 }
347
348 private static void addPossibleResourceDir(Set<String> locations, String s) {
349 if (s != null) {
350 if (!s.endsWith(File.separator)) {
351 s += File.separator;
352 }
353 locations.add(s);
354 }
355 }
356
357 /**
358 * Returns a set of all existing directories where resources could be stored.
359 * @return A set of all existing directories where resources could be stored.
360 */
361 public Collection<String> getAllPossiblePreferenceDirs() {
362 Set<String> locations = new HashSet<>();
363 addPossibleResourceDir(locations, dirs.getPreferencesDirectory(false).getPath());
364 addPossibleResourceDir(locations, dirs.getUserDataDirectory(false).getPath());
365 addPossibleResourceDir(locations, System.getenv("JOSM_RESOURCES"));
366 addPossibleResourceDir(locations, System.getProperty("josm.resources"));
367 if (Main.isPlatformWindows()) {
368 String appdata = System.getenv("APPDATA");
369 if (appdata != null && System.getenv("ALLUSERSPROFILE") != null
370 && appdata.lastIndexOf(File.separator) != -1) {
371 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
372 locations.add(new File(new File(System.getenv("ALLUSERSPROFILE"),
373 appdata), "JOSM").getPath());
374 }
375 } else {
376 locations.add("/usr/local/share/josm/");
377 locations.add("/usr/local/lib/josm/");
378 locations.add("/usr/share/josm/");
379 locations.add("/usr/lib/josm/");
380 }
381 return locations;
382 }
383
384 /**
385 * Gets all normal (string) settings that have a key starting with the prefix
386 * @param prefix The start of the key
387 * @return The key names of the settings
388 */
389 public synchronized Map<String, String> getAllPrefix(final String prefix) {
390 final Map<String, String> all = new TreeMap<>();
391 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
392 if (e.getKey().startsWith(prefix) && (e.getValue() instanceof StringSetting)) {
393 all.put(e.getKey(), ((StringSetting) e.getValue()).getValue());
394 }
395 }
396 return all;
397 }
398
399 /**
400 * Gets all list settings that have a key starting with the prefix
401 * @param prefix The start of the key
402 * @return The key names of the list settings
403 */
404 public synchronized List<String> getAllPrefixCollectionKeys(final String prefix) {
405 final List<String> all = new LinkedList<>();
406 for (Map.Entry<String, Setting<?>> entry : settingsMap.entrySet()) {
407 if (entry.getKey().startsWith(prefix) && entry.getValue() instanceof ListSetting) {
408 all.add(entry.getKey());
409 }
410 }
411 return all;
412 }
413
414 /**
415 * Get all named colors, including customized and the default ones.
416 * @return a map of all named colors (maps preference key to {@link ColorInfo})
417 */
418 public synchronized Map<String, ColorInfo> getAllNamedColors() {
419 final Map<String, ColorInfo> all = new TreeMap<>();
420 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
421 if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
422 continue;
423 Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
424 .map(d -> d.getValue())
425 .map(lst -> ColorInfo.fromPref(lst, false))
426 .ifPresent(info -> all.put(e.getKey(), info));
427 }
428 for (final Entry<String, Setting<?>> e : defaultsMap.entrySet()) {
429 if (!e.getKey().startsWith(NamedColorProperty.NAMED_COLOR_PREFIX))
430 continue;
431 Utils.instanceOfAndCast(e.getValue(), ListSetting.class)
432 .map(d -> d.getValue())
433 .map(lst -> ColorInfo.fromPref(lst, true))
434 .ifPresent(infoDef -> {
435 ColorInfo info = all.get(e.getKey());
436 if (info == null) {
437 all.put(e.getKey(), infoDef);
438 } else {
439 info.setDefaultValue(infoDef.getDefaultValue());
440 }
441 });
442 }
443 return all;
444 }
445
446 /**
447 * Gets all known colors (preferences starting with the color prefix)
448 * @return All colors
449 * @deprecated (since 12987) replaced by {@link #getAllNamedColors()}
450 */
451 @Deprecated
452 public synchronized Map<String, String> getAllColors() {
453 final Map<String, String> all = new TreeMap<>();
454 for (final Entry<String, Setting<?>> e : defaultsMap.entrySet()) {
455 if (e.getKey().startsWith(COLOR_PREFIX) && e.getValue() instanceof StringSetting) {
456 if (e.getKey().startsWith(COLOR_PREFIX+"layer."))
457 continue; // do not add unchanged layer colors
458 StringSetting d = (StringSetting) e.getValue();
459 if (d.getValue() != null) {
460 all.put(e.getKey().substring(6), d.getValue());
461 }
462 }
463 }
464 for (final Entry<String, Setting<?>> e : settingsMap.entrySet()) {
465 if (e.getKey().startsWith(COLOR_PREFIX) && (e.getValue() instanceof StringSetting)) {
466 all.put(e.getKey().substring(6), ((StringSetting) e.getValue()).getValue());
467 }
468 }
469 return all;
470 }
471
472 /**
473 * Called after every put. In case of a problem, do nothing but output the error in log.
474 * @throws IOException if any I/O error occurs
475 */
476 public synchronized void save() throws IOException {
477 save(getPreferenceFile(), settingsMap.entrySet().stream().filter(NO_DEFAULT_SETTINGS_ENTRY), false);
478 }
479
480 /**
481 * Stores the defaults to the defaults file
482 * @throws IOException If the file could not be saved
483 */
484 public synchronized void saveDefaults() throws IOException {
485 save(getDefaultsCacheFile(), defaultsMap.entrySet().stream(), true);
486 }
487
488 protected void save(File prefFile, Stream<Entry<String, Setting<?>>> settings, boolean defaults) throws IOException {
489 if (!defaults) {
490 /* currently unused, but may help to fix configuration issues in future */
491 putInt("josm.version", Version.getInstance().getVersion());
492 }
493
494 File backupFile = new File(prefFile + "_backup");
495
496 // Backup old preferences if there are old preferences
497 if (initSuccessful && prefFile.exists() && prefFile.length() > 0) {
498 Utils.copyFile(prefFile, backupFile);
499 }
500
501 try (PreferencesWriter writer = new PreferencesWriter(
502 new PrintWriter(new File(prefFile + "_tmp"), StandardCharsets.UTF_8.name()), false, defaults)) {
503 writer.write(settings);
504 }
505
506 File tmpFile = new File(prefFile + "_tmp");
507 Utils.copyFile(tmpFile, prefFile);
508 Utils.deleteFile(tmpFile, marktr("Unable to delete temporary file {0}"));
509
510 setCorrectPermissions(prefFile);
511 setCorrectPermissions(backupFile);
512 }
513
514 private static void setCorrectPermissions(File file) {
515 if (!file.setReadable(false, false) && Logging.isTraceEnabled()) {
516 Logging.trace(tr("Unable to set file non-readable {0}", file.getAbsolutePath()));
517 }
518 if (!file.setWritable(false, false) && Logging.isTraceEnabled()) {
519 Logging.trace(tr("Unable to set file non-writable {0}", file.getAbsolutePath()));
520 }
521 if (!file.setExecutable(false, false) && Logging.isTraceEnabled()) {
522 Logging.trace(tr("Unable to set file non-executable {0}", file.getAbsolutePath()));
523 }
524 if (!file.setReadable(true, true) && Logging.isTraceEnabled()) {
525 Logging.trace(tr("Unable to set file readable {0}", file.getAbsolutePath()));
526 }
527 if (!file.setWritable(true, true) && Logging.isTraceEnabled()) {
528 Logging.trace(tr("Unable to set file writable {0}", file.getAbsolutePath()));
529 }
530 }
531
532 /**
533 * Loads preferences from settings file.
534 * @throws IOException if any I/O error occurs while reading the file
535 * @throws SAXException if the settings file does not contain valid XML
536 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
537 */
538 protected void load() throws IOException, SAXException, XMLStreamException {
539 File pref = getPreferenceFile();
540 PreferencesReader.validateXML(pref);
541 PreferencesReader reader = new PreferencesReader(pref, false);
542 reader.parse();
543 settingsMap.clear();
544 settingsMap.putAll(reader.getSettings());
545 removeObsolete(reader.getVersion());
546 }
547
548 /**
549 * Loads default preferences from default settings cache file.
550 *
551 * Discards entries older than {@link #MAX_AGE_DEFAULT_PREFERENCES}.
552 *
553 * @throws IOException if any I/O error occurs while reading the file
554 * @throws SAXException if the settings file does not contain valid XML
555 * @throws XMLStreamException if an XML error occurs while parsing the file (after validation)
556 */
557 protected void loadDefaults() throws IOException, XMLStreamException, SAXException {
558 File def = getDefaultsCacheFile();
559 PreferencesReader.validateXML(def);
560 PreferencesReader reader = new PreferencesReader(def, true);
561 reader.parse();
562 defaultsMap.clear();
563 long minTime = System.currentTimeMillis() / 1000 - MAX_AGE_DEFAULT_PREFERENCES;
564 for (Entry<String, Setting<?>> e : reader.getSettings().entrySet()) {
565 if (e.getValue().getTime() >= minTime) {
566 defaultsMap.put(e.getKey(), e.getValue());
567 }
568 }
569 }
570
571 /**
572 * Loads preferences from XML reader.
573 * @param in XML reader
574 * @throws XMLStreamException if any XML stream error occurs
575 * @throws IOException if any I/O error occurs
576 */
577 public void fromXML(Reader in) throws XMLStreamException, IOException {
578 PreferencesReader reader = new PreferencesReader(in, false);
579 reader.parse();
580 settingsMap.clear();
581 settingsMap.putAll(reader.getSettings());
582 }
583
584 /**
585 * Initializes preferences.
586 * @param reset if {@code true}, current settings file is replaced by the default one
587 */
588 public void init(boolean reset) {
589 initSuccessful = false;
590 // get the preferences.
591 File prefDir = dirs.getPreferencesDirectory(false);
592 if (prefDir.exists()) {
593 if (!prefDir.isDirectory()) {
594 Logging.warn(tr("Failed to initialize preferences. Preference directory ''{0}'' is not a directory.",
595 prefDir.getAbsoluteFile()));
596 JOptionPane.showMessageDialog(
597 Main.parent,
598 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>",
599 prefDir.getAbsoluteFile()),
600 tr("Error"),
601 JOptionPane.ERROR_MESSAGE
602 );
603 return;
604 }
605 } else {
606 if (!prefDir.mkdirs()) {
607 Logging.warn(tr("Failed to initialize preferences. Failed to create missing preference directory: {0}",
608 prefDir.getAbsoluteFile()));
609 JOptionPane.showMessageDialog(
610 Main.parent,
611 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",
612 prefDir.getAbsoluteFile()),
613 tr("Error"),
614 JOptionPane.ERROR_MESSAGE
615 );
616 return;
617 }
618 }
619
620 File preferenceFile = getPreferenceFile();
621 try {
622 if (!preferenceFile.exists()) {
623 Logging.info(tr("Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
624 resetToDefault();
625 save();
626 } else if (reset) {
627 File backupFile = new File(prefDir, "preferences.xml.bak");
628 Main.platform.rename(preferenceFile, backupFile);
629 Logging.warn(tr("Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
630 resetToDefault();
631 save();
632 }
633 } catch (IOException e) {
634 Logging.error(e);
635 JOptionPane.showMessageDialog(
636 Main.parent,
637 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",
638 getPreferenceFile().getAbsoluteFile()),
639 tr("Error"),
640 JOptionPane.ERROR_MESSAGE
641 );
642 return;
643 }
644 try {
645 load();
646 initSuccessful = true;
647 } catch (IOException | SAXException | XMLStreamException e) {
648 Logging.error(e);
649 File backupFile = new File(prefDir, "preferences.xml.bak");
650 JOptionPane.showMessageDialog(
651 Main.parent,
652 tr("<html>Preferences file had errors.<br> Making backup of old one to <br>{0}<br> " +
653 "and creating a new default preference file.</html>",
654 backupFile.getAbsoluteFile()),
655 tr("Error"),
656 JOptionPane.ERROR_MESSAGE
657 );
658 Main.platform.rename(preferenceFile, backupFile);
659 try {
660 resetToDefault();
661 save();
662 } catch (IOException e1) {
663 Logging.error(e1);
664 Logging.warn(tr("Failed to initialize preferences. Failed to reset preference file to default: {0}", getPreferenceFile()));
665 }
666 }
667 File def = getDefaultsCacheFile();
668 if (def.exists()) {
669 try {
670 loadDefaults();
671 } catch (IOException | XMLStreamException | SAXException e) {
672 Logging.error(e);
673 Logging.warn(tr("Failed to load defaults cache file: {0}", def));
674 defaultsMap.clear();
675 if (!def.delete()) {
676 Logging.warn(tr("Failed to delete faulty defaults cache file: {0}", def));
677 }
678 }
679 }
680 }
681
682 /**
683 * Resets the preferences to their initial state. This resets all values and file associations.
684 * The default values and listeners are not removed.
685 * <p>
686 * It is meant to be called before {@link #init(boolean)}
687 * @since 10876
688 */
689 public void resetToInitialState() {
690 resetToDefault();
691 saveOnPut = true;
692 initSuccessful = false;
693 }
694
695 /**
696 * Reset all values stored in this map to the default values. This clears the preferences.
697 */
698 public final void resetToDefault() {
699 settingsMap.clear();
700 }
701
702 /**
703 * Convenience method for accessing colour preferences.
704 * <p>
705 * To be removed: end of 2016
706 *
707 * @param colName name of the colour
708 * @param def default value
709 * @return a Color object for the configured colour, or the default value if none configured.
710 * @deprecated Use a {@link NamedColorProperty} instead.
711 */
712 @Deprecated
713 public synchronized Color getColor(String colName, Color def) {
714 return getColor(colName, null, def);
715 }
716
717 /**
718 * only for preferences
719 * @param o color key
720 * @return translated color name
721 * @deprecated (since 12987) no longer supported
722 */
723 @Deprecated
724 public synchronized String getColorName(String o) {
725 Matcher m = COLOR_LAYER_PATTERN.matcher(o);
726 if (m.matches()) {
727 return tr("Layer: {0}", tr(I18n.escape(m.group(1))));
728 }
729 String fullKey = COLOR_PREFIX + o;
730 if (colornames.containsKey(fullKey)) {
731 String name = colornames.get(fullKey);
732 Matcher m2 = COLOR_MAPPAINT_PATTERN.matcher(name);
733 if (m2.matches()) {
734 return tr("Paint style {0}: {1}", tr(I18n.escape(m2.group(1))), tr(I18n.escape(m2.group(2))));
735 } else {
736 return tr(I18n.escape(colornames.get(fullKey)));
737 }
738 } else {
739 return fullKey;
740 }
741 }
742
743 /**
744 * Convenience method for accessing colour preferences.
745 * <p>
746 * To be removed: end of 2016
747 * @param colName name of the colour
748 * @param specName name of the special colour settings
749 * @param def default value
750 * @return a Color object for the configured colour, or the default value if none configured.
751 * @deprecated Use a {@link NamedColorProperty} instead.
752 * You can replace this by: <code>new NamedColorProperty(colName, def).getChildColor(specName)</code>
753 */
754 @Deprecated
755 public synchronized Color getColor(String colName, String specName, Color def) {
756 String colKey = org.openstreetmap.josm.data.preferences.ColorProperty.getColorKey(colName);
757 registerColor(colKey, colName);
758 String colStr = specName != null ? get(COLOR_PREFIX+specName) : "";
759 if (colStr.isEmpty()) {
760 colStr = get(colKey, ColorHelper.color2html(def, true));
761 }
762 if (colStr != null && !colStr.isEmpty()) {
763 return ColorHelper.html2color(colStr);
764 } else {
765 return def;
766 }
767 }
768
769 /**
770 * Registers a color name conversion for the global color registry.
771 * @param colKey The key
772 * @param colName The name of the color.
773 * @since 10824
774 * @deprecated (since 12987) no longer supported
775 */
776 @Deprecated
777 public void registerColor(String colKey, String colName) {
778 if (!colKey.equals(colName)) {
779 colornames.put(colKey, colName);
780 }
781 }
782
783 /**
784 * Gets the default color that was registered with the preference
785 * @param colKey The color name
786 * @return The color
787 * @deprecated (since 12989) no longer supported
788 */
789 @Deprecated
790 public synchronized Color getDefaultColor(String colKey) {
791 StringSetting col = Utils.cast(defaultsMap.get(COLOR_PREFIX+colKey), StringSetting.class);
792 String colStr = col == null ? null : col.getValue();
793 return colStr == null || colStr.isEmpty() ? null : ColorHelper.html2color(colStr);
794 }
795
796 /**
797 * Stores a color
798 * @param colKey The color name
799 * @param val The color
800 * @return true if the setting was modified
801 * @see NamedColorProperty#put(Color)
802 * @deprecated (since 12987) no longer supported (see {@link NamedColorProperty})
803 */
804 @Deprecated
805 public synchronized boolean putColor(String colKey, Color val) {
806 return put(COLOR_PREFIX+colKey, val != null ? ColorHelper.color2html(val, true) : null);
807 }
808
809 /**
810 * Set a value for a certain setting. The changed setting is saved to the preference file immediately.
811 * Due to caching mechanisms on modern operating systems and hardware, this shouldn't be a performance problem.
812 * @param key the unique identifier for the setting
813 * @param setting the value of the setting. In case it is null, the key-value entry will be removed.
814 * @return {@code true}, if something has changed (i.e. value is different than before)
815 */
816 @Override
817 public boolean putSetting(final String key, Setting<?> setting) {
818 CheckParameterUtil.ensureParameterNotNull(key);
819 if (setting != null && setting.getValue() == null)
820 throw new IllegalArgumentException("setting argument must not have null value");
821 Setting<?> settingOld;
822 Setting<?> settingCopy = null;
823 synchronized (this) {
824 if (setting == null) {
825 settingOld = settingsMap.remove(key);
826 if (settingOld == null)
827 return false;
828 } else {
829 settingOld = settingsMap.get(key);
830 if (setting.equals(settingOld))
831 return false;
832 if (settingOld == null && setting.equals(defaultsMap.get(key)))
833 return false;
834 settingCopy = setting.copy();
835 settingsMap.put(key, settingCopy);
836 }
837 if (saveOnPut) {
838 try {
839 save();
840 } catch (IOException e) {
841 Logging.log(Logging.LEVEL_WARN, tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()), e);
842 }
843 }
844 }
845 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
846 firePreferenceChanged(key, settingOld, settingCopy);
847 return true;
848 }
849
850 /**
851 * Get a setting of any type
852 * @param key The key for the setting
853 * @param def The default value to use if it was not found
854 * @return The setting
855 */
856 public synchronized Setting<?> getSetting(String key, Setting<?> def) {
857 return getSetting(key, def, Setting.class);
858 }
859
860 /**
861 * Get settings value for a certain key and provide default a value.
862 * @param <T> the setting type
863 * @param key the identifier for the setting
864 * @param def the default value. For each call of getSetting() with a given key, the default value must be the same.
865 * <code>def</code> must not be null, but the value of <code>def</code> can be null.
866 * @param klass the setting type (same as T)
867 * @return the corresponding value if the property has been set before, {@code def} otherwise
868 */
869 @SuppressWarnings("unchecked")
870 @Override
871 public synchronized <T extends Setting<?>> T getSetting(String key, T def, Class<T> klass) {
872 CheckParameterUtil.ensureParameterNotNull(key);
873 CheckParameterUtil.ensureParameterNotNull(def);
874 Setting<?> oldDef = defaultsMap.get(key);
875 if (oldDef != null && oldDef.isNew() && oldDef.getValue() != null && def.getValue() != null && !def.equals(oldDef)) {
876 Logging.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
877 }
878 if (def.getValue() != null || oldDef == null) {
879 Setting<?> defCopy = def.copy();
880 defCopy.setTime(System.currentTimeMillis() / 1000);
881 defCopy.setNew(true);
882 defaultsMap.put(key, defCopy);
883 }
884 Setting<?> prop = settingsMap.get(key);
885 if (klass.isInstance(prop)) {
886 return (T) prop;
887 } else {
888 return def;
889 }
890 }
891
892 @Override
893 public Set<String> getKeySet() {
894 return Collections.unmodifiableSet(settingsMap.keySet());
895 }
896
897 /**
898 * Gets a map of all settings that are currently stored
899 * @return The settings
900 */
901 public Map<String, Setting<?>> getAllSettings() {
902 return new TreeMap<>(settingsMap);
903 }
904
905 /**
906 * Gets a map of all currently known defaults
907 * @return The map (key/setting)
908 */
909 public Map<String, Setting<?>> getAllDefaults() {
910 return new TreeMap<>(defaultsMap);
911 }
912
913 /**
914 * Replies the collection of plugin site URLs from where plugin lists can be downloaded.
915 * @return the collection of plugin site URLs
916 * @see #getOnlinePluginSites
917 */
918 public Collection<String> getPluginSites() {
919 return getList("pluginmanager.sites", Collections.singletonList(Main.getJOSMWebsite()+"/pluginicons%<?plugins=>"));
920 }
921
922 /**
923 * Returns the list of plugin sites available according to offline mode settings.
924 * @return the list of available plugin sites
925 * @since 8471
926 */
927 public Collection<String> getOnlinePluginSites() {
928 Collection<String> pluginSites = new ArrayList<>(getPluginSites());
929 for (Iterator<String> it = pluginSites.iterator(); it.hasNext();) {
930 try {
931 OnlineResource.JOSM_WEBSITE.checkOfflineAccess(it.next(), Main.getJOSMWebsite());
932 } catch (OfflineAccessException ex) {
933 Logging.log(Logging.LEVEL_WARN, ex);
934 it.remove();
935 }
936 }
937 return pluginSites;
938 }
939
940 /**
941 * Sets the collection of plugin site URLs.
942 *
943 * @param sites the site URLs
944 */
945 public void setPluginSites(Collection<String> sites) {
946 putList("pluginmanager.sites", new ArrayList<>(sites));
947 }
948
949 /**
950 * Returns XML describing these preferences.
951 * @param nopass if password must be excluded
952 * @return XML
953 */
954 public String toXML(boolean nopass) {
955 return toXML(settingsMap.entrySet(), nopass, false);
956 }
957
958 /**
959 * Returns XML describing the given preferences.
960 * @param settings preferences settings
961 * @param nopass if password must be excluded
962 * @param defaults true, if default values are converted to XML, false for
963 * regular preferences
964 * @return XML
965 */
966 public String toXML(Collection<Entry<String, Setting<?>>> settings, boolean nopass, boolean defaults) {
967 try (
968 StringWriter sw = new StringWriter();
969 PreferencesWriter prefWriter = new PreferencesWriter(new PrintWriter(sw), nopass, defaults)
970 ) {
971 prefWriter.write(settings);
972 sw.flush();
973 return sw.toString();
974 } catch (IOException e) {
975 Logging.error(e);
976 return null;
977 }
978 }
979
980 /**
981 * Removes obsolete preference settings. If you throw out a once-used preference
982 * setting, add it to the list here with an expiry date (written as comment). If you
983 * see something with an expiry date in the past, remove it from the list.
984 * @param loadedVersion JOSM version when the preferences file was written
985 */
986 private void removeObsolete(int loadedVersion) {
987 for (String key : OBSOLETE_PREF_KEYS) {
988 if (settingsMap.containsKey(key)) {
989 settingsMap.remove(key);
990 Logging.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
991 }
992 }
993 }
994
995 /**
996 * Enables or not the preferences file auto-save mechanism (save each time a setting is changed).
997 * This behaviour is enabled by default.
998 * @param enable if {@code true}, makes JOSM save preferences file each time a setting is changed
999 * @since 7085
1000 */
1001 public final void enableSaveOnPut(boolean enable) {
1002 synchronized (this) {
1003 saveOnPut = enable;
1004 }
1005 }
1006}
Note: See TracBrowser for help on using the repository browser.