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

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

see #8465 - global use of try-with-resources, according to

  • Property svn:eol-style set to native
File size: 61.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Toolkit;
8import java.io.BufferedReader;
9import java.io.File;
10import java.io.FileInputStream;
11import java.io.FileOutputStream;
12import java.io.IOException;
13import java.io.InputStream;
14import java.io.InputStreamReader;
15import java.io.OutputStreamWriter;
16import java.io.PrintWriter;
17import java.io.Reader;
18import java.lang.annotation.Retention;
19import java.lang.annotation.RetentionPolicy;
20import java.lang.reflect.Field;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Iterator;
25import java.util.LinkedHashMap;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Map.Entry;
30import java.util.ResourceBundle;
31import java.util.SortedMap;
32import java.util.TreeMap;
33import java.util.concurrent.CopyOnWriteArrayList;
34import java.util.regex.Matcher;
35import java.util.regex.Pattern;
36
37import javax.swing.JOptionPane;
38import javax.swing.UIManager;
39import javax.xml.XMLConstants;
40import javax.xml.stream.XMLInputFactory;
41import javax.xml.stream.XMLStreamConstants;
42import javax.xml.stream.XMLStreamException;
43import javax.xml.stream.XMLStreamReader;
44import javax.xml.transform.stream.StreamSource;
45import javax.xml.validation.Schema;
46import javax.xml.validation.SchemaFactory;
47import javax.xml.validation.Validator;
48
49import org.openstreetmap.josm.Main;
50import org.openstreetmap.josm.data.preferences.ColorProperty;
51import org.openstreetmap.josm.io.MirroredInputStream;
52import org.openstreetmap.josm.io.XmlWriter;
53import org.openstreetmap.josm.tools.CheckParameterUtil;
54import org.openstreetmap.josm.tools.ColorHelper;
55import org.openstreetmap.josm.tools.I18n;
56import org.openstreetmap.josm.tools.Utils;
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 */
79public class Preferences {
80 /**
81 * Internal storage for the preference directory.
82 * Do not access this variable directly!
83 * @see #getPreferencesDirFile()
84 */
85 private File preferencesDirFile = null;
86 /**
87 * Internal storage for the cache directory.
88 */
89 private File cacheDirFile = null;
90
91 /**
92 * Maps the setting name to the current value of the setting.
93 * The map must not contain null as key or value. The mapped setting objects
94 * must not have a null value.
95 */
96 protected final SortedMap<String, Setting<?>> settingsMap = new TreeMap<>();
97
98 /**
99 * Maps the setting name to the default value of the setting.
100 * The map must not contain null as key or value. The value of the mapped
101 * setting objects can be null.
102 */
103 protected final SortedMap<String, Setting<?>> defaultsMap = new TreeMap<>();
104
105 /**
106 * Maps color keys to human readable color name
107 */
108 protected final SortedMap<String, String> colornames = new TreeMap<>();
109
110 /**
111 * Interface for a preference value.
112 *
113 * Implementations must provide a proper <code>equals</code> method.
114 *
115 * @param <T> the data type for the value
116 */
117 public interface Setting<T> {
118 /**
119 * Returns the value of this setting.
120 *
121 * @return the value of this setting
122 */
123 T getValue();
124
125 /**
126 * Check if the value of this Setting object is equal to the given value.
127 * @param otherVal the other value
128 * @return true if the values are equal
129 */
130 boolean equalVal(T otherVal);
131
132 /**
133 * Clone the current object.
134 * @return an identical copy of the current object
135 */
136 Setting<T> copy();
137
138 /**
139 * Enable usage of the visitor pattern.
140 *
141 * @param visitor the visitor
142 */
143 void visit(SettingVisitor visitor);
144
145 /**
146 * Returns a setting whose value is null.
147 *
148 * Cannot be static, because there is no static inheritance.
149 * @return a Setting object that isn't null itself, but returns null
150 * for {@link #getValue()}
151 */
152 Setting<T> getNullInstance();
153 }
154
155 /**
156 * Base abstract class of all settings, holding the setting value.
157 *
158 * @param <T> The setting type
159 */
160 public abstract static class AbstractSetting<T> implements Setting<T> {
161 protected final T value;
162 /**
163 * Constructs a new {@code AbstractSetting} with the given value
164 * @param value The setting value
165 */
166 public AbstractSetting(T value) {
167 this.value = value;
168 }
169 @Override public T getValue() {
170 return value;
171 }
172 @Override public String toString() {
173 return value != null ? value.toString() : "null";
174 }
175 }
176
177 /**
178 * Setting containing a {@link String} value.
179 */
180 public static class StringSetting extends AbstractSetting<String> {
181 /**
182 * Constructs a new {@code StringSetting} with the given value
183 * @param value The setting value
184 */
185 public StringSetting(String value) {
186 super(value);
187 }
188 @Override public boolean equalVal(String otherVal) {
189 if (value == null) return otherVal == null;
190 return value.equals(otherVal);
191 }
192 @Override public StringSetting copy() {
193 return new StringSetting(value);
194 }
195 @Override public void visit(SettingVisitor visitor) {
196 visitor.visit(this);
197 }
198 @Override public StringSetting getNullInstance() {
199 return new StringSetting(null);
200 }
201 @Override
202 public boolean equals(Object other) {
203 if (!(other instanceof StringSetting)) return false;
204 return equalVal(((StringSetting) other).getValue());
205 }
206 }
207
208 /**
209 * Setting containing a {@link List} of {@link String} values.
210 */
211 public static class ListSetting extends AbstractSetting<List<String>> {
212 /**
213 * Constructs a new {@code ListSetting} with the given value
214 * @param value The setting value
215 */
216 public ListSetting(List<String> value) {
217 super(value);
218 consistencyTest();
219 }
220 /**
221 * Convenience factory method.
222 * @param value the value
223 * @return a corresponding ListSetting object
224 */
225 public static ListSetting create(Collection<String> value) {
226 return new ListSetting(value == null ? null : Collections.unmodifiableList(new ArrayList<>(value)));
227 }
228 @Override public boolean equalVal(List<String> otherVal) {
229 return equalCollection(value, otherVal);
230 }
231 public static boolean equalCollection(Collection<String> a, Collection<String> b) {
232 if (a == null) return b == null;
233 if (b == null) return false;
234 if (a.size() != b.size()) return false;
235 Iterator<String> itA = a.iterator();
236 Iterator<String> itB = b.iterator();
237 while (itA.hasNext()) {
238 String aStr = itA.next();
239 String bStr = itB.next();
240 if (!Utils.equal(aStr,bStr)) return false;
241 }
242 return true;
243 }
244 @Override public ListSetting copy() {
245 return ListSetting.create(value);
246 }
247 private void consistencyTest() {
248 if (value != null && value.contains(null))
249 throw new RuntimeException("Error: Null as list element in preference setting");
250 }
251 @Override public void visit(SettingVisitor visitor) {
252 visitor.visit(this);
253 }
254 @Override public ListSetting getNullInstance() {
255 return new ListSetting(null);
256 }
257 @Override
258 public boolean equals(Object other) {
259 if (!(other instanceof ListSetting)) return false;
260 return equalVal(((ListSetting) other).getValue());
261 }
262 }
263
264 /**
265 * Setting containing a {@link List} of {@code List}s of {@link String} values.
266 */
267 public static class ListListSetting extends AbstractSetting<List<List<String>>> {
268
269 /**
270 * Constructs a new {@code ListListSetting} with the given value
271 * @param value The setting value
272 */
273 public ListListSetting(List<List<String>> value) {
274 super(value);
275 consistencyTest();
276 }
277
278 /**
279 * Convenience factory method.
280 * @param value the value
281 * @return a corresponding ListListSetting object
282 */
283 public static ListListSetting create(Collection<Collection<String>> value) {
284 if (value != null) {
285 List<List<String>> valueList = new ArrayList<>(value.size());
286 for (Collection<String> lst : value) {
287 valueList.add(new ArrayList<>(lst));
288 }
289 return new ListListSetting(valueList);
290 }
291 return new ListListSetting(null);
292 }
293
294 @Override
295 public boolean equalVal(List<List<String>> otherVal) {
296 if (value == null) return otherVal == null;
297 if (otherVal == null) return false;
298 if (value.size() != otherVal.size()) return false;
299 Iterator<List<String>> itA = value.iterator();
300 Iterator<List<String>> itB = otherVal.iterator();
301 while (itA.hasNext()) {
302 if (!ListSetting.equalCollection(itA.next(), itB.next())) return false;
303 }
304 return true;
305 }
306
307 @Override
308 public ListListSetting copy() {
309 if (value == null) return new ListListSetting(null);
310
311 List<List<String>> copy = new ArrayList<>(value.size());
312 for (Collection<String> lst : value) {
313 List<String> lstCopy = new ArrayList<>(lst);
314 copy.add(Collections.unmodifiableList(lstCopy));
315 }
316 return new ListListSetting(Collections.unmodifiableList(copy));
317 }
318
319 private void consistencyTest() {
320 if (value == null) return;
321 if (value.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting");
322 for (Collection<String> lst : value) {
323 if (lst.contains(null)) throw new RuntimeException("Error: Null as inner list element in preference setting");
324 }
325 }
326
327 @Override
328 public void visit(SettingVisitor visitor) {
329 visitor.visit(this);
330 }
331
332 @Override
333 public ListListSetting getNullInstance() {
334 return new ListListSetting(null);
335 }
336
337 @Override
338 public boolean equals(Object other) {
339 if (!(other instanceof ListListSetting)) return false;
340 return equalVal(((ListListSetting) other).getValue());
341 }
342 }
343
344 /**
345 * Setting containing a {@link List} of {@link Map}s of {@link String} values.
346 */
347 public static class MapListSetting extends AbstractSetting<List<Map<String, String>>> {
348
349 /**
350 * Constructs a new {@code MapListSetting} with the given value
351 * @param value The setting value
352 */
353 public MapListSetting(List<Map<String, String>> value) {
354 super(value);
355 consistencyTest();
356 }
357
358 @Override
359 public boolean equalVal(List<Map<String, String>> otherVal) {
360 if (value == null) return otherVal == null;
361 if (otherVal == null) return false;
362 if (value.size() != otherVal.size()) return false;
363 Iterator<Map<String, String>> itA = value.iterator();
364 Iterator<Map<String, String>> itB = otherVal.iterator();
365 while (itA.hasNext()) {
366 if (!equalMap(itA.next(), itB.next())) return false;
367 }
368 return true;
369 }
370
371 private static boolean equalMap(Map<String, String> a, Map<String, String> b) {
372 if (a == null) return b == null;
373 if (b == null) return false;
374 if (a.size() != b.size()) return false;
375 for (Entry<String, String> e : a.entrySet()) {
376 if (!Utils.equal(e.getValue(), b.get(e.getKey()))) return false;
377 }
378 return true;
379 }
380
381 @Override
382 public MapListSetting copy() {
383 if (value == null) return new MapListSetting(null);
384 List<Map<String, String>> copy = new ArrayList<>(value.size());
385 for (Map<String, String> map : value) {
386 Map<String, String> mapCopy = new LinkedHashMap<>(map);
387 copy.add(Collections.unmodifiableMap(mapCopy));
388 }
389 return new MapListSetting(Collections.unmodifiableList(copy));
390 }
391
392 private void consistencyTest() {
393 if (value == null) return;
394 if (value.contains(null)) throw new RuntimeException("Error: Null as list element in preference setting");
395 for (Map<String, String> map : value) {
396 if (map.keySet().contains(null)) throw new RuntimeException("Error: Null as map key in preference setting");
397 if (map.values().contains(null)) throw new RuntimeException("Error: Null as map value in preference setting");
398 }
399 }
400
401 @Override
402 public void visit(SettingVisitor visitor) {
403 visitor.visit(this);
404 }
405
406 @Override
407 public MapListSetting getNullInstance() {
408 return new MapListSetting(null);
409 }
410
411 @Override
412 public boolean equals(Object other) {
413 if (!(other instanceof MapListSetting)) return false;
414 return equalVal(((MapListSetting) other).getValue());
415 }
416 }
417
418 public interface SettingVisitor {
419 void visit(StringSetting setting);
420 void visit(ListSetting value);
421 void visit(ListListSetting value);
422 void visit(MapListSetting value);
423 }
424
425 public interface PreferenceChangeEvent {
426 String getKey();
427 Setting<?> getOldValue();
428 Setting<?> getNewValue();
429 }
430
431 public interface PreferenceChangedListener {
432 void preferenceChanged(PreferenceChangeEvent e);
433 }
434
435 private static class DefaultPreferenceChangeEvent implements PreferenceChangeEvent {
436 private final String key;
437 private final Setting<?> oldValue;
438 private final Setting<?> newValue;
439
440 public DefaultPreferenceChangeEvent(String key, Setting<?> oldValue, Setting<?> newValue) {
441 this.key = key;
442 this.oldValue = oldValue;
443 this.newValue = newValue;
444 }
445
446 @Override
447 public String getKey() {
448 return key;
449 }
450
451 @Override
452 public Setting<?> getOldValue() {
453 return oldValue;
454 }
455
456 @Override
457 public Setting<?> getNewValue() {
458 return newValue;
459 }
460 }
461
462 public interface ColorKey {
463 String getColorName();
464 String getSpecialName();
465 Color getDefaultValue();
466 }
467
468 private final CopyOnWriteArrayList<PreferenceChangedListener> listeners = new CopyOnWriteArrayList<>();
469
470 /**
471 * Adds a new preferences listener.
472 * @param listener The listener to add
473 */
474 public void addPreferenceChangeListener(PreferenceChangedListener listener) {
475 if (listener != null) {
476 listeners.addIfAbsent(listener);
477 }
478 }
479
480 /**
481 * Removes a preferences listener.
482 * @param listener The listener to remove
483 */
484 public void removePreferenceChangeListener(PreferenceChangedListener listener) {
485 listeners.remove(listener);
486 }
487
488 protected void firePreferenceChanged(String key, Setting<?> oldValue, Setting<?> newValue) {
489 PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
490 for (PreferenceChangedListener l : listeners) {
491 l.preferenceChanged(evt);
492 }
493 }
494
495 /**
496 * Returns the location of the user defined preferences directory
497 * @return The location of the user defined preferences directory
498 */
499 public String getPreferencesDir() {
500 final String path = getPreferencesDirFile().getPath();
501 if (path.endsWith(File.separator))
502 return path;
503 return path + File.separator;
504 }
505
506 /**
507 * Returns the user defined preferences directory
508 * @return The user defined preferences directory
509 */
510 public File getPreferencesDirFile() {
511 if (preferencesDirFile != null)
512 return preferencesDirFile;
513 String path;
514 path = System.getProperty("josm.home");
515 if (path != null) {
516 preferencesDirFile = new File(path).getAbsoluteFile();
517 } else {
518 path = System.getenv("APPDATA");
519 if (path != null) {
520 preferencesDirFile = new File(path, "JOSM");
521 } else {
522 preferencesDirFile = new File(System.getProperty("user.home"), ".josm");
523 }
524 }
525 return preferencesDirFile;
526 }
527
528 /**
529 * Returns the user preferences file
530 * @return The user preferences file
531 */
532 public File getPreferenceFile() {
533 return new File(getPreferencesDirFile(), "preferences.xml");
534 }
535
536 /**
537 * Returns the user plugin directory
538 * @return The user plugin directory
539 */
540 public File getPluginsDirectory() {
541 return new File(getPreferencesDirFile(), "plugins");
542 }
543
544 /**
545 * Get the directory where cached content of any kind should be stored.
546 *
547 * If the directory doesn't exist on the file system, it will be created
548 * by this method.
549 *
550 * @return the cache directory
551 */
552 public File getCacheDirectory() {
553 if (cacheDirFile != null)
554 return cacheDirFile;
555 String path = System.getProperty("josm.cache");
556 if (path != null) {
557 cacheDirFile = new File(path).getAbsoluteFile();
558 } else {
559 path = get("cache.folder", null);
560 if (path != null) {
561 cacheDirFile = new File(path);
562 } else {
563 cacheDirFile = new File(getPreferencesDirFile(), "cache");
564 }
565 }
566 if (!cacheDirFile.exists() && !cacheDirFile.mkdirs()) {
567 Main.warn(tr("Failed to create missing cache directory: {0}", cacheDirFile.getAbsoluteFile()));
568 JOptionPane.showMessageDialog(
569 Main.parent,
570 tr("<html>Failed to create missing cache directory: {0}</html>", cacheDirFile.getAbsoluteFile()),
571 tr("Error"),
572 JOptionPane.ERROR_MESSAGE
573 );
574 }
575 return cacheDirFile;
576 }
577
578 /**
579 * @return A list of all existing directories where resources could be stored.
580 */
581 public Collection<String> getAllPossiblePreferenceDirs() {
582 LinkedList<String> locations = new LinkedList<>();
583 locations.add(getPreferencesDir());
584 String s;
585 if ((s = System.getenv("JOSM_RESOURCES")) != null) {
586 if (!s.endsWith(File.separator)) {
587 s = s + File.separator;
588 }
589 locations.add(s);
590 }
591 if ((s = System.getProperty("josm.resources")) != null) {
592 if (!s.endsWith(File.separator)) {
593 s = s + File.separator;
594 }
595 locations.add(s);
596 }
597 String appdata = System.getenv("APPDATA");
598 if (System.getenv("ALLUSERSPROFILE") != null && appdata != null
599 && appdata.lastIndexOf(File.separator) != -1) {
600 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
601 locations.add(new File(new File(System.getenv("ALLUSERSPROFILE"),
602 appdata), "JOSM").getPath());
603 }
604 locations.add("/usr/local/share/josm/");
605 locations.add("/usr/local/lib/josm/");
606 locations.add("/usr/share/josm/");
607 locations.add("/usr/lib/josm/");
608 return locations;
609 }
610
611 /**
612 * Get settings value for a certain key.
613 * @param key the identifier for the setting
614 * @return "" if there is nothing set for the preference key,
615 * the corresponding value otherwise. The result is not null.
616 */
617 public synchronized String get(final String key) {
618 String value = get(key, null);
619 return value == null ? "" : value;
620 }
621
622 /**
623 * Get settings value for a certain key and provide default a value.
624 * @param key the identifier for the setting
625 * @param def the default value. For each call of get() with a given key, the
626 * default value must be the same.
627 * @return the corresponding value if the property has been set before,
628 * def otherwise
629 */
630 public synchronized String get(final String key, final String def) {
631 return getSetting(key, new StringSetting(def), StringSetting.class).getValue();
632 }
633
634 public synchronized Map<String, String> getAllPrefix(final String prefix) {
635 final Map<String,String> all = new TreeMap<>();
636 for (final Entry<String,Setting<?>> e : settingsMap.entrySet()) {
637 if (e.getKey().startsWith(prefix) && (e.getValue() instanceof StringSetting)) {
638 all.put(e.getKey(), ((StringSetting) e.getValue()).getValue());
639 }
640 }
641 return all;
642 }
643
644 public synchronized List<String> getAllPrefixCollectionKeys(final String prefix) {
645 final List<String> all = new LinkedList<>();
646 for (Map.Entry<String, Setting<?>> entry : settingsMap.entrySet()) {
647 if (entry.getKey().startsWith(prefix) && entry.getValue() instanceof ListSetting) {
648 all.add(entry.getKey());
649 }
650 }
651 return all;
652 }
653
654 public synchronized Map<String, String> getAllColors() {
655 final Map<String,String> all = new TreeMap<>();
656 for (final Entry<String,Setting<?>> e : defaultsMap.entrySet()) {
657 if (e.getKey().startsWith("color.") && e.getValue() instanceof StringSetting) {
658 StringSetting d = (StringSetting) e.getValue();
659 if (d.getValue() != null) {
660 all.put(e.getKey().substring(6), d.getValue());
661 }
662 }
663 }
664 for (final Entry<String,Setting<?>> e : settingsMap.entrySet()) {
665 if (e.getKey().startsWith("color.") && (e.getValue() instanceof StringSetting)) {
666 all.put(e.getKey().substring(6), ((StringSetting) e.getValue()).getValue());
667 }
668 }
669 return all;
670 }
671
672 public synchronized boolean getBoolean(final String key) {
673 String s = get(key, null);
674 return s == null ? false : Boolean.parseBoolean(s);
675 }
676
677 public synchronized boolean getBoolean(final String key, final boolean def) {
678 return Boolean.parseBoolean(get(key, Boolean.toString(def)));
679 }
680
681 public synchronized boolean getBoolean(final String key, final String specName, final boolean def) {
682 boolean generic = getBoolean(key, def);
683 String skey = key+"."+specName;
684 Setting<?> prop = settingsMap.get(skey);
685 if (prop instanceof StringSetting)
686 return Boolean.parseBoolean(((StringSetting)prop).getValue());
687 else
688 return generic;
689 }
690
691 /**
692 * Set a value for a certain setting.
693 * @param key the unique identifier for the setting
694 * @param value the value of the setting. Can be null or "" which both removes
695 * the key-value entry.
696 * @return true, if something has changed (i.e. value is different than before)
697 */
698 public boolean put(final String key, String value) {
699 if(value != null && value.length() == 0) {
700 value = null;
701 }
702 return putSetting(key, value == null ? null : new StringSetting(value));
703 }
704
705 public boolean put(final String key, final boolean value) {
706 return put(key, Boolean.toString(value));
707 }
708
709 public boolean putInteger(final String key, final Integer value) {
710 return put(key, Integer.toString(value));
711 }
712
713 public boolean putDouble(final String key, final Double value) {
714 return put(key, Double.toString(value));
715 }
716
717 public boolean putLong(final String key, final Long value) {
718 return put(key, Long.toString(value));
719 }
720
721 /**
722 * Called after every put. In case of a problem, do nothing but output the error in log.
723 */
724 public void save() throws IOException {
725 /* currently unused, but may help to fix configuration issues in future */
726 putInteger("josm.version", Version.getInstance().getVersion());
727
728 updateSystemProperties();
729
730 File prefFile = getPreferenceFile();
731 File backupFile = new File(prefFile + "_backup");
732
733 // Backup old preferences if there are old preferences
734 if (prefFile.exists()) {
735 Utils.copyFile(prefFile, backupFile);
736 }
737
738 try (PrintWriter out = new PrintWriter(new OutputStreamWriter(
739 new FileOutputStream(prefFile + "_tmp"), Utils.UTF_8), false)) {
740 out.print(toXML(false));
741 }
742
743 File tmpFile = new File(prefFile + "_tmp");
744 Utils.copyFile(tmpFile, prefFile);
745 tmpFile.delete();
746
747 setCorrectPermissions(prefFile);
748 setCorrectPermissions(backupFile);
749 }
750
751 private void setCorrectPermissions(File file) {
752 file.setReadable(false, false);
753 file.setWritable(false, false);
754 file.setExecutable(false, false);
755 file.setReadable(true, true);
756 file.setWritable(true, true);
757 }
758
759 public void load() throws Exception {
760 settingsMap.clear();
761 File pref = getPreferenceFile();
762 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(pref), Utils.UTF_8))) {
763 validateXML(in);
764 }
765 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(pref), Utils.UTF_8))) {
766 fromXML(in);
767 }
768 updateSystemProperties();
769 removeObsolete();
770 }
771
772 public void init(boolean reset){
773 // get the preferences.
774 File prefDir = getPreferencesDirFile();
775 if (prefDir.exists()) {
776 if(!prefDir.isDirectory()) {
777 Main.warn(tr("Failed to initialize preferences. Preference directory ''{0}'' is not a directory.", prefDir.getAbsoluteFile()));
778 JOptionPane.showMessageDialog(
779 Main.parent,
780 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>", prefDir.getAbsoluteFile()),
781 tr("Error"),
782 JOptionPane.ERROR_MESSAGE
783 );
784 return;
785 }
786 } else {
787 if (! prefDir.mkdirs()) {
788 Main.warn(tr("Failed to initialize preferences. Failed to create missing preference directory: {0}", prefDir.getAbsoluteFile()));
789 JOptionPane.showMessageDialog(
790 Main.parent,
791 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",prefDir.getAbsoluteFile()),
792 tr("Error"),
793 JOptionPane.ERROR_MESSAGE
794 );
795 return;
796 }
797 }
798
799 File preferenceFile = getPreferenceFile();
800 try {
801 if (!preferenceFile.exists()) {
802 Main.info(tr("Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
803 resetToDefault();
804 save();
805 } else if (reset) {
806 Main.warn(tr("Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
807 resetToDefault();
808 save();
809 }
810 } catch(IOException e) {
811 Main.error(e);
812 JOptionPane.showMessageDialog(
813 Main.parent,
814 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",getPreferenceFile().getAbsoluteFile()),
815 tr("Error"),
816 JOptionPane.ERROR_MESSAGE
817 );
818 return;
819 }
820 try {
821 load();
822 } catch (Exception e) {
823 Main.error(e);
824 File backupFile = new File(prefDir,"preferences.xml.bak");
825 JOptionPane.showMessageDialog(
826 Main.parent,
827 tr("<html>Preferences file had errors.<br> Making backup of old one to <br>{0}<br> and creating a new default preference file.</html>", backupFile.getAbsoluteFile()),
828 tr("Error"),
829 JOptionPane.ERROR_MESSAGE
830 );
831 Main.platform.rename(preferenceFile, backupFile);
832 try {
833 resetToDefault();
834 save();
835 } catch(IOException e1) {
836 Main.error(e1);
837 Main.warn(tr("Failed to initialize preferences. Failed to reset preference file to default: {0}", getPreferenceFile()));
838 }
839 }
840 }
841
842 public final void resetToDefault(){
843 settingsMap.clear();
844 }
845
846 /**
847 * Convenience method for accessing colour preferences.
848 *
849 * @param colName name of the colour
850 * @param def default value
851 * @return a Color object for the configured colour, or the default value if none configured.
852 */
853 public synchronized Color getColor(String colName, Color def) {
854 return getColor(colName, null, def);
855 }
856
857 public synchronized Color getUIColor(String colName) {
858 return UIManager.getColor(colName);
859 }
860
861 /* only for preferences */
862 public synchronized String getColorName(String o) {
863 try {
864 Matcher m = Pattern.compile("mappaint\\.(.+?)\\.(.+)").matcher(o);
865 if (m.matches()) {
866 return tr("Paint style {0}: {1}", tr(I18n.escape(m.group(1))), tr(I18n.escape(m.group(2))));
867 }
868 } catch (Exception e) {
869 Main.warn(e);
870 }
871 try {
872 Matcher m = Pattern.compile("layer (.+)").matcher(o);
873 if (m.matches()) {
874 return tr("Layer: {0}", tr(I18n.escape(m.group(1))));
875 }
876 } catch (Exception e) {
877 Main.warn(e);
878 }
879 return tr(I18n.escape(colornames.containsKey(o) ? colornames.get(o) : o));
880 }
881
882 /**
883 * Returns the color for the given key.
884 * @param key The color key
885 * @return the color
886 */
887 public Color getColor(ColorKey key) {
888 return getColor(key.getColorName(), key.getSpecialName(), key.getDefaultValue());
889 }
890
891 /**
892 * Convenience method for accessing colour preferences.
893 *
894 * @param colName name of the colour
895 * @param specName name of the special colour settings
896 * @param def default value
897 * @return a Color object for the configured colour, or the default value if none configured.
898 */
899 public synchronized Color getColor(String colName, String specName, Color def) {
900 String colKey = ColorProperty.getColorKey(colName);
901 if(!colKey.equals(colName)) {
902 colornames.put(colKey, colName);
903 }
904 String colStr = specName != null ? get("color."+specName) : "";
905 if (colStr.isEmpty()) {
906 colStr = get("color." + colKey, ColorHelper.color2html(def, true));
907 }
908 if (colStr != null && !colStr.isEmpty()) {
909 return ColorHelper.html2color(colStr);
910 } else {
911 return def;
912 }
913 }
914
915 public synchronized Color getDefaultColor(String colKey) {
916 StringSetting col = Utils.cast(defaultsMap.get("color."+colKey), StringSetting.class);
917 String colStr = col == null ? null : col.getValue();
918 return colStr == null || colStr.isEmpty() ? null : ColorHelper.html2color(colStr);
919 }
920
921 public synchronized boolean putColor(String colKey, Color val) {
922 return put("color."+colKey, val != null ? ColorHelper.color2html(val, true) : null);
923 }
924
925 public synchronized int getInteger(String key, int def) {
926 String v = get(key, Integer.toString(def));
927 if(v.isEmpty())
928 return def;
929
930 try {
931 return Integer.parseInt(v);
932 } catch(NumberFormatException e) {
933 // fall out
934 }
935 return def;
936 }
937
938 public synchronized int getInteger(String key, String specName, int def) {
939 String v = get(key+"."+specName);
940 if(v.isEmpty())
941 v = get(key,Integer.toString(def));
942 if(v.isEmpty())
943 return def;
944
945 try {
946 return Integer.parseInt(v);
947 } catch(NumberFormatException e) {
948 // fall out
949 }
950 return def;
951 }
952
953 public synchronized long getLong(String key, long def) {
954 String v = get(key, Long.toString(def));
955 if(null == v)
956 return def;
957
958 try {
959 return Long.parseLong(v);
960 } catch(NumberFormatException e) {
961 // fall out
962 }
963 return def;
964 }
965
966 public synchronized double getDouble(String key, double def) {
967 String v = get(key, Double.toString(def));
968 if(null == v)
969 return def;
970
971 try {
972 return Double.parseDouble(v);
973 } catch(NumberFormatException e) {
974 // fall out
975 }
976 return def;
977 }
978
979 /**
980 * Get a list of values for a certain key
981 * @param key the identifier for the setting
982 * @param def the default value.
983 * @return the corresponding value if the property has been set before,
984 * def otherwise
985 */
986 public Collection<String> getCollection(String key, Collection<String> def) {
987 return getSetting(key, ListSetting.create(def), ListSetting.class).getValue();
988 }
989
990 /**
991 * Get a list of values for a certain key
992 * @param key the identifier for the setting
993 * @return the corresponding value if the property has been set before,
994 * an empty Collection otherwise.
995 */
996 public Collection<String> getCollection(String key) {
997 Collection<String> val = getCollection(key, null);
998 return val == null ? Collections.<String>emptyList() : val;
999 }
1000
1001 public synchronized void removeFromCollection(String key, String value) {
1002 List<String> a = new ArrayList<>(getCollection(key, Collections.<String>emptyList()));
1003 a.remove(value);
1004 putCollection(key, a);
1005 }
1006
1007 /**
1008 * Set a value for a certain setting. The changed setting is saved
1009 * to the preference file immediately. Due to caching mechanisms on modern
1010 * operating systems and hardware, this shouldn't be a performance problem.
1011 * @param key the unique identifier for the setting
1012 * @param setting the value of the setting. In case it is null, the key-value
1013 * entry will be removed.
1014 * @return true, if something has changed (i.e. value is different than before)
1015 */
1016 public boolean putSetting(final String key, Setting<?> setting) {
1017 CheckParameterUtil.ensureParameterNotNull(key);
1018 if (setting != null && setting.getValue() == null)
1019 throw new IllegalArgumentException("setting argument must not have null value");
1020 Setting<?> settingOld;
1021 Setting<?> settingCopy = null;
1022 synchronized (this) {
1023 if (setting == null) {
1024 settingOld = settingsMap.remove(key);
1025 if (settingOld == null)
1026 return false;
1027 } else {
1028 settingOld = settingsMap.get(key);
1029 if (setting.equals(settingOld))
1030 return false;
1031 if (settingOld == null && setting.equals(defaultsMap.get(key)))
1032 return false;
1033 settingCopy = setting.copy();
1034 settingsMap.put(key, settingCopy);
1035 }
1036 try {
1037 save();
1038 } catch (IOException e){
1039 Main.warn(tr("Failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
1040 }
1041 }
1042 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
1043 firePreferenceChanged(key, settingOld, settingCopy);
1044 return true;
1045 }
1046
1047 public synchronized Setting<?> getSetting(String key, Setting<?> def) {
1048 return getSetting(key, def, Setting.class);
1049 }
1050
1051 /**
1052 * Get settings value for a certain key and provide default a value.
1053 * @param <T> the setting type
1054 * @param key the identifier for the setting
1055 * @param def the default value. For each call of getSetting() with a given
1056 * key, the default value must be the same. <code>def</code> must not be
1057 * null, but the value of <code>def</code> can be null.
1058 * @param klass the setting type (same as T)
1059 * @return the corresponding value if the property has been set before,
1060 * def otherwise
1061 */
1062 @SuppressWarnings("unchecked")
1063 public synchronized <T extends Setting<?>> T getSetting(String key, T def, Class<T> klass) {
1064 CheckParameterUtil.ensureParameterNotNull(key);
1065 CheckParameterUtil.ensureParameterNotNull(def);
1066 Setting<?> oldDef = defaultsMap.get(key);
1067 if (oldDef != null && oldDef.getValue() != null && def.getValue() != null && !def.equals(oldDef)) {
1068 Main.info("Defaults for " + key + " differ: " + def + " != " + defaultsMap.get(key));
1069 }
1070 if (def.getValue() != null || oldDef == null) {
1071 defaultsMap.put(key, def.copy());
1072 }
1073 Setting<?> prop = settingsMap.get(key);
1074 if (klass.isInstance(prop)) {
1075 return (T) prop;
1076 } else {
1077 return def;
1078 }
1079 }
1080
1081 public boolean putCollection(String key, Collection<String> value) {
1082 return putSetting(key, value == null ? null : ListSetting.create(value));
1083 }
1084
1085 /**
1086 * Saves at most {@code maxsize} items of collection {@code val}.
1087 */
1088 public boolean putCollectionBounded(String key, int maxsize, Collection<String> val) {
1089 Collection<String> newCollection = new ArrayList<>(Math.min(maxsize, val.size()));
1090 for (String i : val) {
1091 if (newCollection.size() >= maxsize) {
1092 break;
1093 }
1094 newCollection.add(i);
1095 }
1096 return putCollection(key, newCollection);
1097 }
1098
1099 /**
1100 * Used to read a 2-dimensional array of strings from the preference file.
1101 * If not a single entry could be found, <code>def</code> is returned.
1102 */
1103 @SuppressWarnings({ "unchecked", "rawtypes" })
1104 public synchronized Collection<Collection<String>> getArray(String key, Collection<Collection<String>> def) {
1105 ListListSetting val = getSetting(key, ListListSetting.create(def), ListListSetting.class);
1106 return (Collection) val.getValue();
1107 }
1108
1109 public Collection<Collection<String>> getArray(String key) {
1110 Collection<Collection<String>> res = getArray(key, null);
1111 return res == null ? Collections.<Collection<String>>emptyList() : res;
1112 }
1113
1114 public boolean putArray(String key, Collection<Collection<String>> value) {
1115 return putSetting(key, value == null ? null : ListListSetting.create(value));
1116 }
1117
1118 public Collection<Map<String, String>> getListOfStructs(String key, Collection<Map<String, String>> def) {
1119 return getSetting(key, new MapListSetting(def == null ? null : new ArrayList<>(def)), MapListSetting.class).getValue();
1120 }
1121
1122 public boolean putListOfStructs(String key, Collection<Map<String, String>> value) {
1123 return putSetting(key, value == null ? null : new MapListSetting(new ArrayList<>(value)));
1124 }
1125
1126 @Retention(RetentionPolicy.RUNTIME) public @interface pref { }
1127 @Retention(RetentionPolicy.RUNTIME) public @interface writeExplicitly { }
1128
1129 /**
1130 * Get a list of hashes which are represented by a struct-like class.
1131 * Possible properties are given by fields of the class klass that have
1132 * the @pref annotation.
1133 * Default constructor is used to initialize the struct objects, properties
1134 * then override some of these default values.
1135 * @param key main preference key
1136 * @param klass The struct class
1137 * @return a list of objects of type T or an empty list if nothing was found
1138 */
1139 public <T> List<T> getListOfStructs(String key, Class<T> klass) {
1140 List<T> r = getListOfStructs(key, null, klass);
1141 if (r == null)
1142 return Collections.emptyList();
1143 else
1144 return r;
1145 }
1146
1147 /**
1148 * same as above, but returns def if nothing was found
1149 */
1150 public <T> List<T> getListOfStructs(String key, Collection<T> def, Class<T> klass) {
1151 Collection<Map<String,String>> prop =
1152 getListOfStructs(key, def == null ? null : serializeListOfStructs(def, klass));
1153 if (prop == null)
1154 return def == null ? null : new ArrayList<>(def);
1155 List<T> lst = new ArrayList<>();
1156 for (Map<String,String> entries : prop) {
1157 T struct = deserializeStruct(entries, klass);
1158 lst.add(struct);
1159 }
1160 return lst;
1161 }
1162
1163 /**
1164 * Save a list of hashes represented by a struct-like class.
1165 * Considers only fields that have the @pref annotation.
1166 * In addition it does not write fields with null values. (Thus they are cleared)
1167 * Default values are given by the field values after default constructor has
1168 * been called.
1169 * Fields equal to the default value are not written unless the field has
1170 * the @writeExplicitly annotation.
1171 * @param key main preference key
1172 * @param val the list that is supposed to be saved
1173 * @param klass The struct class
1174 * @return true if something has changed
1175 */
1176 public <T> boolean putListOfStructs(String key, Collection<T> val, Class<T> klass) {
1177 return putListOfStructs(key, serializeListOfStructs(val, klass));
1178 }
1179
1180 private <T> Collection<Map<String,String>> serializeListOfStructs(Collection<T> l, Class<T> klass) {
1181 if (l == null)
1182 return null;
1183 Collection<Map<String,String>> vals = new ArrayList<>();
1184 for (T struct : l) {
1185 if (struct == null) {
1186 continue;
1187 }
1188 vals.add(serializeStruct(struct, klass));
1189 }
1190 return vals;
1191 }
1192
1193 public static <T> Map<String,String> serializeStruct(T struct, Class<T> klass) {
1194 T structPrototype;
1195 try {
1196 structPrototype = klass.newInstance();
1197 } catch (InstantiationException | IllegalAccessException ex) {
1198 throw new RuntimeException(ex);
1199 }
1200
1201 Map<String,String> hash = new LinkedHashMap<>();
1202 for (Field f : klass.getDeclaredFields()) {
1203 if (f.getAnnotation(pref.class) == null) {
1204 continue;
1205 }
1206 f.setAccessible(true);
1207 try {
1208 Object fieldValue = f.get(struct);
1209 Object defaultFieldValue = f.get(structPrototype);
1210 if (fieldValue != null) {
1211 if (f.getAnnotation(writeExplicitly.class) != null || !Utils.equal(fieldValue, defaultFieldValue)) {
1212 hash.put(f.getName().replace("_", "-"), fieldValue.toString());
1213 }
1214 }
1215 } catch (IllegalArgumentException | IllegalAccessException ex) {
1216 throw new RuntimeException(ex);
1217 }
1218 }
1219 return hash;
1220 }
1221
1222 public static <T> T deserializeStruct(Map<String,String> hash, Class<T> klass) {
1223 T struct = null;
1224 try {
1225 struct = klass.newInstance();
1226 } catch (InstantiationException | IllegalAccessException ex) {
1227 throw new RuntimeException(ex);
1228 }
1229 for (Entry<String,String> key_value : hash.entrySet()) {
1230 Object value = null;
1231 Field f;
1232 try {
1233 f = klass.getDeclaredField(key_value.getKey().replace("-", "_"));
1234 } catch (NoSuchFieldException ex) {
1235 continue;
1236 } catch (SecurityException ex) {
1237 throw new RuntimeException(ex);
1238 }
1239 if (f.getAnnotation(pref.class) == null) {
1240 continue;
1241 }
1242 f.setAccessible(true);
1243 if (f.getType() == Boolean.class || f.getType() == boolean.class) {
1244 value = Boolean.parseBoolean(key_value.getValue());
1245 } else if (f.getType() == Integer.class || f.getType() == int.class) {
1246 try {
1247 value = Integer.parseInt(key_value.getValue());
1248 } catch (NumberFormatException nfe) {
1249 continue;
1250 }
1251 } else if (f.getType() == Double.class || f.getType() == double.class) {
1252 try {
1253 value = Double.parseDouble(key_value.getValue());
1254 } catch (NumberFormatException nfe) {
1255 continue;
1256 }
1257 } else if (f.getType() == String.class) {
1258 value = key_value.getValue();
1259 } else
1260 throw new RuntimeException("unsupported preference primitive type");
1261
1262 try {
1263 f.set(struct, value);
1264 } catch (IllegalArgumentException ex) {
1265 throw new AssertionError(ex);
1266 } catch (IllegalAccessException ex) {
1267 throw new RuntimeException(ex);
1268 }
1269 }
1270 return struct;
1271 }
1272
1273 public Map<String, Setting<?>> getAllSettings() {
1274 return new TreeMap<>(settingsMap);
1275 }
1276
1277 public Map<String, Setting<?>> getAllDefaults() {
1278 return new TreeMap<>(defaultsMap);
1279 }
1280
1281 /**
1282 * Updates system properties with the current values in the preferences.
1283 *
1284 */
1285 public void updateSystemProperties() {
1286 if(getBoolean("prefer.ipv6", false)) {
1287 // never set this to false, only true!
1288 updateSystemProperty("java.net.preferIPv6Addresses", "true");
1289 }
1290 updateSystemProperty("http.agent", Version.getInstance().getAgentString());
1291 updateSystemProperty("user.language", get("language"));
1292 // Workaround to fix a Java bug.
1293 // Force AWT toolkit to update its internal preferences (fix #3645).
1294 // This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739
1295 try {
1296 Field field = Toolkit.class.getDeclaredField("resources");
1297 field.setAccessible(true);
1298 field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt"));
1299 } catch (Exception e) {
1300 // Ignore all exceptions
1301 }
1302 // Workaround to fix a Java "feature"
1303 // See http://stackoverflow.com/q/7615645/2257172 and #9875
1304 if (getBoolean("jdk.tls.disableSNIExtension", true)) {
1305 updateSystemProperty("jsse.enableSNIExtension", "false");
1306 }
1307 // Workaround to fix another Java bug
1308 // Force Java 7 to use old sorting algorithm of Arrays.sort (fix #8712).
1309 // See Oracle bug database: https://bugs.openjdk.java.net/browse/JDK-7075600
1310 // and https://bugs.openjdk.java.net/browse/JDK-6923200
1311 if (getBoolean("jdk.Arrays.useLegacyMergeSort", !Version.getInstance().isLocalBuild())) {
1312 updateSystemProperty("java.util.Arrays.useLegacyMergeSort", "true");
1313 }
1314 }
1315
1316 /**
1317 * Updates a given system property.
1318 * @param key The property key
1319 * @param value The property value
1320 * @return the previous value of the system property, or {@code null} if it did not have one.
1321 * @since 6851
1322 */
1323 public static String updateSystemProperty(String key, String value) {
1324 if (value != null) {
1325 String old = System.setProperty(key, value);
1326 if (!key.toLowerCase().contains("password")) {
1327 Main.debug("System property '"+key+"' set to '"+value+"'. Old value was '"+old+"'");
1328 } else {
1329 Main.debug("System property '"+key+"' changed.");
1330 }
1331 return old;
1332 }
1333 return null;
1334 }
1335
1336 /**
1337 * Replies the collection of plugin site URLs from where plugin lists can be downloaded.
1338 * @return the collection of plugin site URLs
1339 */
1340 public Collection<String> getPluginSites() {
1341 return getCollection("pluginmanager.sites", Collections.singleton(Main.getJOSMWebsite()+"/plugin%<?plugins=>"));
1342 }
1343
1344 /**
1345 * Sets the collection of plugin site URLs.
1346 *
1347 * @param sites the site URLs
1348 */
1349 public void setPluginSites(Collection<String> sites) {
1350 putCollection("pluginmanager.sites", sites);
1351 }
1352
1353 protected XMLStreamReader parser;
1354
1355 public void validateXML(Reader in) throws Exception {
1356 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
1357 try (InputStream xsdStream = new MirroredInputStream("resource://data/preferences.xsd")) {
1358 Schema schema = factory.newSchema(new StreamSource(xsdStream));
1359 Validator validator = schema.newValidator();
1360 validator.validate(new StreamSource(in));
1361 }
1362 }
1363
1364 public void fromXML(Reader in) throws XMLStreamException {
1365 XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(in);
1366 this.parser = parser;
1367 parse();
1368 }
1369
1370 public void parse() throws XMLStreamException {
1371 int event = parser.getEventType();
1372 while (true) {
1373 if (event == XMLStreamConstants.START_ELEMENT) {
1374 parseRoot();
1375 } else if (event == XMLStreamConstants.END_ELEMENT) {
1376 return;
1377 }
1378 if (parser.hasNext()) {
1379 event = parser.next();
1380 } else {
1381 break;
1382 }
1383 }
1384 parser.close();
1385 }
1386
1387 public void parseRoot() throws XMLStreamException {
1388 while (true) {
1389 int event = parser.next();
1390 if (event == XMLStreamConstants.START_ELEMENT) {
1391 String localName = parser.getLocalName();
1392 switch(localName) {
1393 case "tag":
1394 settingsMap.put(parser.getAttributeValue(null, "key"), new StringSetting(parser.getAttributeValue(null, "value")));
1395 jumpToEnd();
1396 break;
1397 case "list":
1398 case "collection":
1399 case "lists":
1400 case "maps":
1401 parseToplevelList();
1402 break;
1403 default:
1404 throwException("Unexpected element: "+localName);
1405 }
1406 } else if (event == XMLStreamConstants.END_ELEMENT) {
1407 return;
1408 }
1409 }
1410 }
1411
1412 private void jumpToEnd() throws XMLStreamException {
1413 while (true) {
1414 int event = parser.next();
1415 if (event == XMLStreamConstants.START_ELEMENT) {
1416 jumpToEnd();
1417 } else if (event == XMLStreamConstants.END_ELEMENT) {
1418 return;
1419 }
1420 }
1421 }
1422
1423 protected void parseToplevelList() throws XMLStreamException {
1424 String key = parser.getAttributeValue(null, "key");
1425 String name = parser.getLocalName();
1426
1427 List<String> entries = null;
1428 List<List<String>> lists = null;
1429 List<Map<String, String>> maps = null;
1430 while (true) {
1431 int event = parser.next();
1432 if (event == XMLStreamConstants.START_ELEMENT) {
1433 String localName = parser.getLocalName();
1434 switch(localName) {
1435 case "entry":
1436 if (entries == null) {
1437 entries = new ArrayList<>();
1438 }
1439 entries.add(parser.getAttributeValue(null, "value"));
1440 jumpToEnd();
1441 break;
1442 case "list":
1443 if (lists == null) {
1444 lists = new ArrayList<>();
1445 }
1446 lists.add(parseInnerList());
1447 break;
1448 case "map":
1449 if (maps == null) {
1450 maps = new ArrayList<>();
1451 }
1452 maps.add(parseMap());
1453 break;
1454 default:
1455 throwException("Unexpected element: "+localName);
1456 }
1457 } else if (event == XMLStreamConstants.END_ELEMENT) {
1458 break;
1459 }
1460 }
1461 if (entries != null) {
1462 settingsMap.put(key, new ListSetting(Collections.unmodifiableList(entries)));
1463 } else if (lists != null) {
1464 settingsMap.put(key, new ListListSetting(Collections.unmodifiableList(lists)));
1465 } else if (maps != null) {
1466 settingsMap.put(key, new MapListSetting(Collections.unmodifiableList(maps)));
1467 } else {
1468 if ("lists".equals(name)) {
1469 settingsMap.put(key, new ListListSetting(Collections.<List<String>>emptyList()));
1470 } else if ("maps".equals(name)) {
1471 settingsMap.put(key, new MapListSetting(Collections.<Map<String, String>>emptyList()));
1472 } else {
1473 settingsMap.put(key, new ListSetting(Collections.<String>emptyList()));
1474 }
1475 }
1476 }
1477
1478 protected List<String> parseInnerList() throws XMLStreamException {
1479 List<String> entries = new ArrayList<>();
1480 while (true) {
1481 int event = parser.next();
1482 if (event == XMLStreamConstants.START_ELEMENT) {
1483 if ("entry".equals(parser.getLocalName())) {
1484 entries.add(parser.getAttributeValue(null, "value"));
1485 jumpToEnd();
1486 } else {
1487 throwException("Unexpected element: "+parser.getLocalName());
1488 }
1489 } else if (event == XMLStreamConstants.END_ELEMENT) {
1490 break;
1491 }
1492 }
1493 return Collections.unmodifiableList(entries);
1494 }
1495
1496 protected Map<String, String> parseMap() throws XMLStreamException {
1497 Map<String, String> map = new LinkedHashMap<>();
1498 while (true) {
1499 int event = parser.next();
1500 if (event == XMLStreamConstants.START_ELEMENT) {
1501 if ("tag".equals(parser.getLocalName())) {
1502 map.put(parser.getAttributeValue(null, "key"), parser.getAttributeValue(null, "value"));
1503 jumpToEnd();
1504 } else {
1505 throwException("Unexpected element: "+parser.getLocalName());
1506 }
1507 } else if (event == XMLStreamConstants.END_ELEMENT) {
1508 break;
1509 }
1510 }
1511 return Collections.unmodifiableMap(map);
1512 }
1513
1514 protected void throwException(String msg) {
1515 throw new RuntimeException(msg + tr(" (at line {0}, column {1})", parser.getLocation().getLineNumber(), parser.getLocation().getColumnNumber()));
1516 }
1517
1518 private class SettingToXml implements SettingVisitor {
1519 private StringBuilder b;
1520 private boolean noPassword;
1521 private String key;
1522
1523 public SettingToXml(StringBuilder b, boolean noPassword) {
1524 this.b = b;
1525 this.noPassword = noPassword;
1526 }
1527
1528 public void setKey(String key) {
1529 this.key = key;
1530 }
1531
1532 @Override
1533 public void visit(StringSetting setting) {
1534 if (noPassword && "osm-server.password".equals(key))
1535 return; // do not store plain password.
1536 /* don't save default values */
1537 if (setting.equals(defaultsMap.get(key)))
1538 return;
1539 b.append(" <tag key='");
1540 b.append(XmlWriter.encode(key));
1541 b.append("' value='");
1542 b.append(XmlWriter.encode(setting.getValue()));
1543 b.append("'/>\n");
1544 }
1545
1546 @Override
1547 public void visit(ListSetting setting) {
1548 /* don't save default values */
1549 if (setting.equals(defaultsMap.get(key)))
1550 return;
1551 b.append(" <list key='").append(XmlWriter.encode(key)).append("'>\n");
1552 for (String s : setting.getValue()) {
1553 b.append(" <entry value='").append(XmlWriter.encode(s)).append("'/>\n");
1554 }
1555 b.append(" </list>\n");
1556 }
1557
1558 @Override
1559 public void visit(ListListSetting setting) {
1560 /* don't save default values */
1561 if (setting.equals(defaultsMap.get(key)))
1562 return;
1563 b.append(" <lists key='").append(XmlWriter.encode(key)).append("'>\n");
1564 for (List<String> list : setting.getValue()) {
1565 b.append(" <list>\n");
1566 for (String s : list) {
1567 b.append(" <entry value='").append(XmlWriter.encode(s)).append("'/>\n");
1568 }
1569 b.append(" </list>\n");
1570 }
1571 b.append(" </lists>\n");
1572 }
1573
1574 @Override
1575 public void visit(MapListSetting setting) {
1576 b.append(" <maps key='").append(XmlWriter.encode(key)).append("'>\n");
1577 for (Map<String, String> struct : setting.getValue()) {
1578 b.append(" <map>\n");
1579 for (Entry<String, String> e : struct.entrySet()) {
1580 b.append(" <tag key='").append(XmlWriter.encode(e.getKey())).append("' value='").append(XmlWriter.encode(e.getValue())).append("'/>\n");
1581 }
1582 b.append(" </map>\n");
1583 }
1584 b.append(" </maps>\n");
1585 }
1586 }
1587
1588 public String toXML(boolean nopass) {
1589 StringBuilder b = new StringBuilder(
1590 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
1591 "<preferences xmlns=\""+Main.getXMLBase()+"/preferences-1.0\" version=\""+
1592 Version.getInstance().getVersion() + "\">\n");
1593 SettingToXml toXml = new SettingToXml(b, nopass);
1594 for (Entry<String, Setting<?>> e : settingsMap.entrySet()) {
1595 toXml.setKey(e.getKey());
1596 e.getValue().visit(toXml);
1597 }
1598 b.append("</preferences>\n");
1599 return b.toString();
1600 }
1601
1602 /**
1603 * Removes obsolete preference settings. If you throw out a once-used preference
1604 * setting, add it to the list here with an expiry date (written as comment). If you
1605 * see something with an expiry date in the past, remove it from the list.
1606 */
1607 public void removeObsolete() {
1608 /* update the data with old consumer key*/
1609 if(getInteger("josm.version", Version.getInstance().getVersion()) < 6076) {
1610 if(!get("oauth.access-token.key").isEmpty() && get("oauth.settings.consumer-key").isEmpty()) {
1611 put("oauth.settings.consumer-key", "AdCRxTpvnbmfV8aPqrTLyA");
1612 put("oauth.settings.consumer-secret", "XmYOiGY9hApytcBC3xCec3e28QBqOWz5g6DSb5UpE");
1613 }
1614 }
1615
1616 String[] obsolete = {
1617 "downloadAlong.downloadAlongTrack.distance", // 07/2013 - can be removed mid-2014. Replaced by downloadAlongWay.distance
1618 "downloadAlong.downloadAlongTrack.area", // 07/2013 - can be removed mid-2014. Replaced by downloadAlongWay.area
1619 "gpxLayer.downloadAlongTrack.distance", // 07/2013 - can be removed mid-2014. Replaced by downloadAlongTrack.distance
1620 "gpxLayer.downloadAlongTrack.area", // 07/2013 - can be removed mid-2014. Replaced by downloadAlongTrack.area
1621 "gpxLayer.downloadAlongTrack.near", // 07/2013 - can be removed mid-2014. Replaced by downloadAlongTrack.near
1622 "validator.tests", // 01/2014 - can be removed end-2014. Replaced by validator.skip
1623 "validator.testsBeforeUpload", // 01/2014 - can be removed end-2014. Replaced by validator.skipBeforeUpload
1624 "validator.TagChecker.sources", // 01/2014 - can be removed end-2014. Replaced by validator.TagChecker.source
1625 "validator.TagChecker.usedatafile", // 01/2014 - can be removed end-2014. Replaced by validator.TagChecker.source
1626 "validator.TagChecker.useignorefile", // 01/2014 - can be removed end-2014. Replaced by validator.TagChecker.source
1627 "validator.TagChecker.usespellfile", // 01/2014 - can be removed end-2014. Replaced by validator.TagChecker.source
1628 "validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.sources" // 01/2014 - can be removed end-2014. Replaced by validator.org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker.entries
1629 };
1630 for (String key : obsolete) {
1631 if (settingsMap.containsKey(key)) {
1632 settingsMap.remove(key);
1633 Main.info(tr("Preference setting {0} has been removed since it is no longer used.", key));
1634 }
1635 }
1636 }
1637
1638 public static boolean isEqual(Setting<?> a, Setting<?> b) {
1639 if (a == null) return b == null;
1640 return a.equals(b);
1641 }
1642}
Note: See TracBrowser for help on using the repository browser.