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

Last change on this file since 2053 was 2053, checked in by Gubaer, 15 years ago

fixed #2322: Regression in r2038 (patches "JOSM destroys its preferences, when disk is full")

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