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

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

Minor mappaint cleanup, use constants for colors

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