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

Last change on this file since 2655 was 2655, checked in by jttt, 14 years ago

Fixed #4161 Major slowdown in recent versions, used correct pattern for listeners realized using CopyOnWriteArrayList

  • Property svn:eol-style set to native
File size: 25.6 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.io.BufferedReader;
8import java.io.File;
9import java.io.FileInputStream;
10import java.io.FileOutputStream;
11import java.io.IOException;
12import java.io.InputStreamReader;
13import java.io.OutputStreamWriter;
14import java.io.PrintWriter;
15import java.nio.channels.FileChannel;
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23import java.util.Properties;
24import java.util.SortedMap;
25import java.util.TreeMap;
26import java.util.Map.Entry;
27import java.util.concurrent.CopyOnWriteArrayList;
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30
31import javax.swing.JOptionPane;
32
33import org.openstreetmap.josm.Main;
34import org.openstreetmap.josm.tools.ColorHelper;
35
36/**
37 * This class holds all preferences for JOSM.
38 *
39 * Other classes can register their beloved properties here. All properties will be
40 * saved upon set-access.
41 *
42 * @author imi
43 */
44public class Preferences {
45 //static private final Logger logger = Logger.getLogger(Preferences.class.getName());
46
47 /**
48 * Internal storage for the preference directory.
49 * Do not access this variable directly!
50 * @see #getPreferencesDirFile()
51 */
52 private File preferencesDirFile = null;
53
54 public static interface PreferenceChangeEvent{
55 public String getKey();
56 public String getOldValue();
57 public String getNewValue();
58 }
59
60 public static interface PreferenceChangedListener {
61 void preferenceChanged(PreferenceChangeEvent e);
62 }
63
64 private static class DefaultPreferenceChangeEvent implements PreferenceChangeEvent {
65 private final String key;
66 private final String oldValue;
67 private final String newValue;
68
69 public DefaultPreferenceChangeEvent(String key, String oldValue, String newValue) {
70 this.key = key;
71 this.oldValue = oldValue;
72 this.newValue = newValue;
73 }
74
75 public String getKey() {
76 return key;
77 }
78 public String getOldValue() {
79 return oldValue;
80 }
81 public String getNewValue() {
82 return newValue;
83 }
84 }
85
86 /**
87 * Class holding one bookmarkentry.
88 * @author imi
89 */
90 public static class Bookmark implements Comparable<Bookmark> {
91 private String name;
92 private Bounds area;
93
94 public Bookmark() {
95 area = null;
96 name = null;
97 }
98
99 public Bookmark(Bounds area) {
100 this.area = area;
101 }
102
103 @Override public String toString() {
104 return name;
105 }
106
107 public int compareTo(Bookmark b) {
108 return name.toLowerCase().compareTo(b.name.toLowerCase());
109 }
110
111 public Bounds getArea() {
112 return area;
113 }
114
115 public String getName() {
116 return name;
117 }
118
119 public void setName(String name) {
120 this.name = name;
121 }
122
123 public void setArea(Bounds area) {
124 this.area = area;
125 }
126 }
127
128 private final CopyOnWriteArrayList<PreferenceChangedListener> listeners = new CopyOnWriteArrayList<PreferenceChangedListener>();
129
130
131 public void addPreferenceChangeListener(PreferenceChangedListener listener) {
132 if (listener != null) {
133 listeners.addIfAbsent(listener);
134 }
135 }
136
137 public void removePreferenceChangeListener(PreferenceChangedListener listener) {
138 listeners.remove(listener);
139 }
140
141 protected void firePrefrenceChanged(String key, String oldValue, String newValue) {
142 PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
143 for (PreferenceChangedListener l : listeners) {
144 l.preferenceChanged(evt);
145 }
146 }
147
148 /**
149 * Map the property name to the property object.
150 */
151 protected final SortedMap<String, String> properties = new TreeMap<String, String>();
152 protected final SortedMap<String, String> defaults = new TreeMap<String, String>();
153
154 /**
155 * Override some values on read. This is intended to be used for technology previews
156 * where we want to temporarily modify things without changing the user's preferences
157 * file.
158 */
159 protected static final SortedMap<String, String> override = new TreeMap<String, String>();
160 static {
161 //override.put("osm-server.version", "0.5");
162 //override.put("osm-server.additional-versions", "");
163 //override.put("osm-server.url", "http://openstreetmap.gryph.de/api");
164 //override.put("plugins", null);
165 }
166
167 /**
168 * Return the location of the user defined preferences file
169 */
170 public String getPreferencesDir() {
171 final String path = getPreferencesDirFile().getPath();
172 if (path.endsWith(File.separator))
173 return path;
174 return path + File.separator;
175 }
176
177 public File getPreferencesDirFile() {
178 if (preferencesDirFile != null)
179 return preferencesDirFile;
180 String path;
181 path = System.getProperty("josm.home");
182 if (path != null) {
183 preferencesDirFile = new File(path);
184 } else {
185 path = System.getenv("APPDATA");
186 if (path != null) {
187 preferencesDirFile = new File(path, "JOSM");
188 } else {
189 preferencesDirFile = new File(System.getProperty("user.home"), ".josm");
190 }
191 }
192 return preferencesDirFile;
193 }
194
195 public File getPreferenceFile() {
196 return new File(getPreferencesDirFile(), "preferences");
197 }
198
199 public File getPluginsDirFile() {
200 return new File(getPreferencesDirFile(), "plugins");
201 }
202
203 /**
204 * @return A list of all existing directories where resources could be stored.
205 */
206 public Collection<String> getAllPossiblePreferenceDirs() {
207 LinkedList<String> locations = new LinkedList<String>();
208 locations.add(Main.pref.getPreferencesDir());
209 String s;
210 if ((s = System.getenv("JOSM_RESOURCES")) != null) {
211 if (!s.endsWith(File.separator)) {
212 s = s + File.separator;
213 }
214 locations.add(s);
215 }
216 if ((s = System.getProperty("josm.resources")) != null) {
217 if (!s.endsWith(File.separator)) {
218 s = s + File.separator;
219 }
220 locations.add(s);
221 }
222 String appdata = System.getenv("APPDATA");
223 if (System.getenv("ALLUSERSPROFILE") != null && appdata != null
224 && appdata.lastIndexOf(File.separator) != -1) {
225 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
226 locations.add(new File(new File(System.getenv("ALLUSERSPROFILE"),
227 appdata), "JOSM").getPath());
228 }
229 locations.add("/usr/local/share/josm/");
230 locations.add("/usr/local/lib/josm/");
231 locations.add("/usr/share/josm/");
232 locations.add("/usr/lib/josm/");
233 return locations;
234 }
235
236 synchronized public boolean hasKey(final String key) {
237 return override.containsKey(key) ? override.get(key) != null : properties.containsKey(key);
238 }
239
240 synchronized public String get(final String key) {
241 putDefault(key, null);
242 if (override.containsKey(key))
243 return override.get(key);
244 if (!properties.containsKey(key))
245 return "";
246 return properties.get(key);
247 }
248
249 synchronized public String get(final String key, final String def) {
250 putDefault(key, def);
251 if (override.containsKey(key))
252 return override.get(key);
253 final String prop = properties.get(key);
254 if (prop == null || prop.equals(""))
255 return def;
256 return prop;
257 }
258
259 synchronized public Map<String, String> getAllPrefix(final String prefix) {
260 final Map<String,String> all = new TreeMap<String,String>();
261 for (final Entry<String,String> e : properties.entrySet())
262 if (e.getKey().startsWith(prefix)) {
263 all.put(e.getKey(), e.getValue());
264 }
265 for (final Entry<String,String> e : override.entrySet())
266 if (e.getKey().startsWith(prefix))
267 if (e.getValue() == null) {
268 all.remove(e.getKey());
269 } else {
270 all.put(e.getKey(), e.getValue());
271 }
272 return all;
273 }
274
275 synchronized public TreeMap<String, String> getAllColors() {
276 final TreeMap<String,String> all = new TreeMap<String,String>();
277 for (final Entry<String,String> e : defaults.entrySet())
278 if (e.getKey().startsWith("color.") && e.getValue() != null) {
279 all.put(e.getKey().substring(6), e.getValue());
280 }
281 for (final Entry<String,String> e : properties.entrySet())
282 if (e.getKey().startsWith("color.")) {
283 all.put(e.getKey().substring(6), e.getValue());
284 }
285 for (final Entry<String,String> e : override.entrySet())
286 if (e.getKey().startsWith("color."))
287 if (e.getValue() == null) {
288 all.remove(e.getKey().substring(6));
289 } else {
290 all.put(e.getKey().substring(6), e.getValue());
291 }
292 return all;
293 }
294
295 synchronized public Map<String, String> getDefaults() {
296 return defaults;
297 }
298
299 synchronized public void putDefault(final String key, final String def) {
300 if(!defaults.containsKey(key) || defaults.get(key) == null) {
301 defaults.put(key, def);
302 } else if(def != null && !defaults.get(key).equals(def)) {
303 System.out.println("Defaults for " + key + " differ: " + def + " != " + defaults.get(key));
304 }
305 }
306
307 synchronized public boolean getBoolean(final String key) {
308 putDefault(key, null);
309 if (override.containsKey(key))
310 return override.get(key) == null ? false : Boolean.parseBoolean(override.get(key));
311 return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : false;
312 }
313
314 synchronized public boolean getBoolean(final String key, final boolean def) {
315 putDefault(key, Boolean.toString(def));
316 if (override.containsKey(key))
317 return override.get(key) == null ? def : Boolean.parseBoolean(override.get(key));
318 return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : def;
319 }
320
321 synchronized public boolean put(final String key, String value) {
322 String oldvalue = properties.get(key);
323 if(value != null && value.length() == 0) {
324 value = null;
325 }
326 if(!((oldvalue == null && (value == null || value.equals(defaults.get(key))))
327 || (value != null && oldvalue != null && oldvalue.equals(value))))
328 {
329 if (value == null) {
330 properties.remove(key);
331 } else {
332 properties.put(key, value);
333 }
334 try {
335 save();
336 } catch(IOException e){
337 System.out.println(tr("Warning: failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
338 }
339 firePrefrenceChanged(key, oldvalue, value);
340 return true;
341 }
342 return false;
343 }
344
345 synchronized public boolean put(final String key, final boolean value) {
346 return put(key, Boolean.toString(value));
347 }
348
349 synchronized public boolean putInteger(final String key, final Integer value) {
350 return put(key, Integer.toString(value));
351 }
352
353 synchronized public boolean putDouble(final String key, final Double value) {
354 return put(key, Double.toString(value));
355 }
356
357 synchronized public boolean putLong(final String key, final Long value) {
358 return put(key, Long.toString(value));
359 }
360
361
362 /**
363 * Called after every put. In case of a problem, do nothing but output the error
364 * in log.
365 */
366 public void save() throws IOException {
367 /* currently unused, but may help to fix configuration issues in future */
368 putInteger("josm.version", Version.getInstance().getVersion());
369
370 updateSystemProperties();
371 File prefFile = new File(getPreferencesDirFile(), "preferences");
372
373 // Backup old preferences if there are old preferences
374 if(prefFile.exists()) {
375 copyFile(prefFile, new File(prefFile + "_backup"));
376 }
377
378 final PrintWriter out = new PrintWriter(new OutputStreamWriter(
379 new FileOutputStream(prefFile + "_tmp"), "utf-8"), false);
380 for (final Entry<String, String> e : properties.entrySet()) {
381 String s = defaults.get(e.getKey());
382 /* don't save default values */
383 if(s == null || !s.equals(e.getValue())) {
384 out.println(e.getKey() + "=" + e.getValue());
385 }
386 }
387 out.close();
388
389 File tmpFile = new File(prefFile + "_tmp");
390 copyFile(tmpFile, prefFile);
391 tmpFile.delete();
392 }
393
394 /**
395 * Simple file copy function that will overwrite the target file
396 * Taken from http://www.rgagnon.com/javadetails/java-0064.html (CC-NC-BY-SA)
397 * @param in
398 * @param out
399 * @throws IOException
400 */
401 public static void copyFile(File in, File out) throws IOException {
402 FileChannel inChannel = new FileInputStream(in).getChannel();
403 FileChannel outChannel = new FileOutputStream(out).getChannel();
404 try {
405 inChannel.transferTo(0, inChannel.size(),
406 outChannel);
407 }
408 catch (IOException e) {
409 throw e;
410 }
411 finally {
412 if (inChannel != null) {
413 inChannel.close();
414 }
415 if (outChannel != null) {
416 outChannel.close();
417 }
418 }
419 }
420
421 public void load() throws IOException {
422 properties.clear();
423 final BufferedReader in = new BufferedReader(new InputStreamReader(
424 new FileInputStream(getPreferencesDir()+"preferences"), "utf-8"));
425 int lineNumber = 0;
426 ArrayList<Integer> errLines = new ArrayList<Integer>();
427 for (String line = in.readLine(); line != null; line = in.readLine(), lineNumber++) {
428 final int i = line.indexOf('=');
429 if (i == -1 || i == 0) {
430 errLines.add(lineNumber);
431 continue;
432 }
433 properties.put(line.substring(0,i), line.substring(i+1));
434 }
435 if (!errLines.isEmpty())
436 throw new IOException(tr("Malformed config file at lines {0}", errLines));
437 updateSystemProperties();
438 }
439
440 public void init(boolean reset){
441 // get the preferences.
442 File prefDir = getPreferencesDirFile();
443 if (prefDir.exists()) {
444 if(!prefDir.isDirectory()) {
445 System.err.println(tr("Warning: Failed to initialize preferences. Preference directory ''{0}'' isn't a directory.", prefDir.getAbsoluteFile()));
446 JOptionPane.showMessageDialog(
447 Main.parent,
448 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' isn't a directory.</html>", prefDir.getAbsoluteFile()),
449 tr("Error"),
450 JOptionPane.ERROR_MESSAGE
451 );
452 return;
453 }
454 } else {
455 if (! prefDir.mkdirs()) {
456 System.err.println(tr("Warning: Failed to initialize preferences. Failed to create missing preference directory: {0}", prefDir.getAbsoluteFile()));
457 JOptionPane.showMessageDialog(
458 Main.parent,
459 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",prefDir.getAbsoluteFile()),
460 tr("Error"),
461 JOptionPane.ERROR_MESSAGE
462 );
463 return;
464 }
465 }
466
467 File preferenceFile = getPreferenceFile();
468 try {
469 if (!preferenceFile.exists()) {
470 System.out.println(tr("Warning: Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
471 resetToDefault();
472 save();
473 } else if (reset) {
474 System.out.println(tr("Warning: Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
475 resetToDefault();
476 save();
477 }
478 } catch(IOException e) {
479 e.printStackTrace();
480 JOptionPane.showMessageDialog(
481 Main.parent,
482 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",getPreferenceFile().getAbsoluteFile()),
483 tr("Error"),
484 JOptionPane.ERROR_MESSAGE
485 );
486 return;
487 }
488 try {
489 load();
490 } catch (IOException e) {
491 e.printStackTrace();
492 File backupFile = new File(prefDir,"preferences.bak");
493 JOptionPane.showMessageDialog(
494 Main.parent,
495 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()),
496 tr("Error"),
497 JOptionPane.ERROR_MESSAGE
498 );
499 preferenceFile.renameTo(backupFile);
500 try {
501 resetToDefault();
502 save();
503 } catch(IOException e1) {
504 e1.printStackTrace();
505 System.err.println(tr("Warning: Failed to initialize preferences.Failed to reset preference file to default: {0}", getPreferenceFile()));
506 }
507 }
508 }
509
510 public final void resetToDefault(){
511 properties.clear();
512 }
513
514 public File getBookmarksFile() {
515 return new File(getPreferencesDir(),"bookmarks");
516 }
517
518 public Collection<Bookmark> loadBookmarks() throws IOException {
519 File bookmarkFile = getBookmarksFile();
520 if (!bookmarkFile.exists()) {
521 bookmarkFile.createNewFile();
522 }
523 BufferedReader in = new BufferedReader(new InputStreamReader(
524 new FileInputStream(bookmarkFile), "utf-8"));
525
526 LinkedList<Bookmark> bookmarks = new LinkedList<Bookmark>();
527 for (String line = in.readLine(); line != null; line = in.readLine()) {
528 // FIXME: legacy code using ',' sign, should be \u001e only
529 Matcher m = Pattern.compile("^(.+)[,\u001e](-?\\d+.\\d+)[,\u001e](-?\\d+.\\d+)[,\u001e](-?\\d+.\\d+)[,\u001e](-?\\d+.\\d+)$").matcher(line);
530 if (!m.matches() || m.groupCount() != 5) {
531 System.err.println(tr("Error: Unexpected line ''{0}'' in bookmark file ''{1}''",line, bookmarkFile.toString()));
532 continue;
533 }
534 Bookmark b = new Bookmark();
535 b.setName(m.group(1));
536 double[] values= new double[4];
537 for (int i = 0; i < 4; ++i) {
538 try {
539 values[i] = Double.parseDouble(m.group(i+2));
540 } catch(NumberFormatException e) {
541 System.err.println(tr("Error: Illegal double value ''{0}'' on line ''{1}'' in bookmark file ''{2}''",m.group(i+2),line, bookmarkFile.toString()));
542 continue;
543 }
544 }
545 b.setArea(new Bounds(values));
546 bookmarks.add(b);
547 }
548 in.close();
549 Collections.sort(bookmarks);
550 return bookmarks;
551 }
552
553 public void saveBookmarks(Collection<Bookmark> bookmarks) throws IOException {
554 File bookmarkFile = new File(Main.pref.getPreferencesDir()+"bookmarks");
555 if (!bookmarkFile.exists()) {
556 bookmarkFile.createNewFile();
557 }
558 PrintWriter out = new PrintWriter(new OutputStreamWriter(
559 new FileOutputStream(bookmarkFile), "utf-8"));
560 for (Bookmark b : bookmarks) {
561 out.print(b.getName()+ "\u001e");
562 Bounds area = b.getArea();
563 out.print(area.getMin().lat() +"\u001e");
564 out.print(area.getMin().lon() +"\u001e");
565 out.print(area.getMax().lat() +"\u001e");
566 out.print(area.getMax().lon());
567 out.println();
568 }
569 out.close();
570 }
571
572 /**
573 * Convenience method for accessing colour preferences.
574 *
575 * @param colName name of the colour
576 * @param def default value
577 * @return a Color object for the configured colour, or the default value if none configured.
578 */
579 synchronized public Color getColor(String colName, Color def) {
580 return getColor(colName, null, def);
581 }
582
583 /**
584 * Convenience method for accessing colour preferences.
585 *
586 * @param colName name of the colour
587 * @param specName name of the special colour settings
588 * @param def default value
589 * @return a Color object for the configured colour, or the default value if none configured.
590 */
591 synchronized public Color getColor(String colName, String specName, Color def) {
592 putDefault("color."+colName, ColorHelper.color2html(def));
593 String colStr = specName != null ? get("color."+specName) : "";
594 if(colStr.equals("")) {
595 colStr = get("color."+colName);
596 }
597 return colStr.equals("") ? def : ColorHelper.html2color(colStr);
598 }
599
600 synchronized public Color getDefaultColor(String colName) {
601 String colStr = defaults.get("color."+colName);
602 return colStr.equals("") ? null : ColorHelper.html2color(colStr);
603 }
604
605 synchronized public boolean putColor(String colName, Color val) {
606 return put("color."+colName, val != null ? ColorHelper.color2html(val) : null);
607 }
608
609 synchronized public int getInteger(String key, int def) {
610 putDefault(key, Integer.toString(def));
611 String v = get(key);
612 if(null == v)
613 return def;
614
615 try {
616 return Integer.parseInt(v);
617 } catch(NumberFormatException e) {
618 // fall out
619 }
620 return def;
621 }
622
623 synchronized public long getLong(String key, long def) {
624 putDefault(key, Long.toString(def));
625 String v = get(key);
626 if(null == v)
627 return def;
628
629 try {
630 return Long.parseLong(v);
631 } catch(NumberFormatException e) {
632 // fall out
633 }
634 return def;
635 }
636
637 synchronized public double getDouble(String key, double def) {
638 putDefault(key, Double.toString(def));
639 String v = get(key);
640 if(null == v)
641 return def;
642
643 try {
644 return Double.parseDouble(v);
645 } catch(NumberFormatException e) {
646 // fall out
647 }
648 return def;
649 }
650
651 synchronized public double getDouble(String key, String def) {
652 putDefault(key, def);
653 String v = get(key);
654 if(v != null && v.length() != 0) {
655 try { return Double.parseDouble(v); } catch(NumberFormatException e) {}
656 }
657 try { return Double.parseDouble(def); } catch(NumberFormatException e) {}
658 return 0.0;
659 }
660
661 synchronized public String getCollectionAsString(final String key) {
662 String s = get(key);
663 if(s != null && s.length() != 0) {
664 s = s.replaceAll("\u001e",",");
665 }
666 return s;
667 }
668
669 public boolean isCollection(String key, boolean def) {
670 String s = get(key);
671 if (s != null && s.length() != 0)
672 return s.indexOf("\u001e") >= 0 || s.indexOf("§§§") >= 0;
673 else
674 return def;
675 }
676
677 synchronized public Collection<String> getCollection(String key, Collection<String> def) {
678 String s = get(key);
679 if(def != null)
680 {
681 String d = null;
682 for(String a : def)
683 {
684 if(d != null) {
685 d += "\u001e" + a;
686 } else {
687 d = a;
688 }
689 }
690 putDefault(key, d);
691 }
692 if(s != null && s.length() != 0)
693 {
694 if(s.indexOf("\u001e") < 0) /* FIXME: legacy code, remove later */
695 {
696 String r =s;
697 if(r.indexOf("§§§") > 0) {
698 r = r.replaceAll("§§§","\u001e");
699 } else {
700 r = r.replace(';','\u001e');
701 }
702 if(!r.equals(s)) /* save the converted string */
703 {
704 put(key,r);
705 s = r;
706 }
707 }
708 return Arrays.asList(s.split("\u001e"));
709 }
710 return def;
711 }
712 synchronized public void removeFromCollection(String key, String value) {
713 List<String> a = new ArrayList<String>(getCollection(key, null));
714 a.remove(value);
715 putCollection(key, a);
716 }
717 synchronized public boolean putCollection(String key, Collection<String> val) {
718 String s = null;
719 if(val != null)
720 {
721 for(String a : val)
722 {
723 if(s != null) {
724 s += "\u001e" + a;
725 } else {
726 s = a;
727 }
728 }
729 }
730 return put(key, s);
731 }
732
733 /**
734 * Updates system properties with the current values in the preferences.
735 *
736 */
737 public void updateSystemProperties() {
738 Properties sysProp = System.getProperties();
739 sysProp.put("http.agent", Version.getInstance().getAgentString());
740 System.setProperties(sysProp);
741 }
742}
Note: See TracBrowser for help on using the repository browser.