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

Last change on this file since 3671 was 3549, checked in by stoecker, 14 years ago

document code for array prefs

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