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

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

fix array preferences

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