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

Last change on this file since 3938 was 3938, checked in by stoecker, 13 years ago

fix applet preferences, move XML code to Preferences, as we may want to use it as standard format soon

  • Property svn:eol-style set to native
File size: 35.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.io.Reader;
16import java.lang.annotation.Retention;
17import java.lang.annotation.RetentionPolicy;
18import java.lang.reflect.Field;
19import java.nio.channels.FileChannel;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.LinkedList;
25import java.util.List;
26import java.util.Map;
27import java.util.Properties;
28import java.util.SortedMap;
29import java.util.TreeMap;
30import java.util.Map.Entry;
31import java.util.concurrent.CopyOnWriteArrayList;
32
33import javax.swing.JOptionPane;
34
35import org.openstreetmap.josm.io.XmlWriter;
36import org.openstreetmap.josm.Main;
37import org.openstreetmap.josm.tools.ColorHelper;
38import org.openstreetmap.josm.tools.Utils;
39import org.openstreetmap.josm.tools.XmlObjectParser;
40import org.xml.sax.SAXException;
41
42/**
43 * This class holds all preferences for JOSM.
44 *
45 * Other classes can register their beloved properties here. All properties will be
46 * saved upon set-access.
47 *
48 * Each property is a simple key=value pair of Strings.
49 * In addition, each key has a unique default value that is set when the value is first
50 * accessed using one of the get...() methods. You can use the same preference
51 * key in different parts of the code, but the default value must be the same
52 * everywhere. null is a legitimate default value.
53 *
54 * At the moment, there is no such thing as an empty value.
55 * If you put "" or null as value, the property is removed.
56 *
57 * @author imi
58 */
59public class Preferences {
60 //static private final Logger logger = Logger.getLogger(Preferences.class.getName());
61
62 /**
63 * Internal storage for the preference directory.
64 * Do not access this variable directly!
65 * @see #getPreferencesDirFile()
66 */
67 private File preferencesDirFile = null;
68
69 /**
70 * Map the property name to the property object. Does not contain null or "" values.
71 */
72 protected final SortedMap<String, String> properties = new TreeMap<String, String>();
73 protected final SortedMap<String, String> defaults = new TreeMap<String, String>();
74
75 public interface PreferenceChangeEvent{
76 String getKey();
77 String getOldValue();
78 String getNewValue();
79 }
80
81 public interface PreferenceChangedListener {
82 void preferenceChanged(PreferenceChangeEvent e);
83 }
84
85 private static class DefaultPreferenceChangeEvent implements PreferenceChangeEvent {
86 private final String key;
87 private final String oldValue;
88 private final String newValue;
89
90 public DefaultPreferenceChangeEvent(String key, String oldValue, String newValue) {
91 this.key = key;
92 this.oldValue = oldValue;
93 this.newValue = newValue;
94 }
95
96 public String getKey() {
97 return key;
98 }
99 public String getOldValue() {
100 return oldValue;
101 }
102 public String getNewValue() {
103 return newValue;
104 }
105 }
106
107 public interface ColorKey {
108 String getColorName();
109 String getSpecialName();
110 Color getDefault();
111 }
112
113 private final CopyOnWriteArrayList<PreferenceChangedListener> listeners = new CopyOnWriteArrayList<PreferenceChangedListener>();
114
115 public void addPreferenceChangeListener(PreferenceChangedListener listener) {
116 if (listener != null) {
117 listeners.addIfAbsent(listener);
118 }
119 }
120
121 public void removePreferenceChangeListener(PreferenceChangedListener listener) {
122 listeners.remove(listener);
123 }
124
125 protected void firePreferenceChanged(String key, String oldValue, String newValue) {
126 PreferenceChangeEvent evt = new DefaultPreferenceChangeEvent(key, oldValue, newValue);
127 for (PreferenceChangedListener l : listeners) {
128 l.preferenceChanged(evt);
129 }
130 }
131
132 /**
133 * Return the location of the user defined preferences file
134 */
135 public String getPreferencesDir() {
136 final String path = getPreferencesDirFile().getPath();
137 if (path.endsWith(File.separator))
138 return path;
139 return path + File.separator;
140 }
141
142 public File getPreferencesDirFile() {
143 if (preferencesDirFile != null)
144 return preferencesDirFile;
145 String path;
146 path = System.getProperty("josm.home");
147 if (path != null) {
148 preferencesDirFile = new File(path);
149 } else {
150 path = System.getenv("APPDATA");
151 if (path != null) {
152 preferencesDirFile = new File(path, "JOSM");
153 } else {
154 preferencesDirFile = new File(System.getProperty("user.home"), ".josm");
155 }
156 }
157 return preferencesDirFile;
158 }
159
160 public File getPreferenceFile() {
161 return new File(getPreferencesDirFile(), "preferences");
162 }
163
164 public File getPluginsDirectory() {
165 return new File(getPreferencesDirFile(), "plugins");
166 }
167
168 /**
169 * @return A list of all existing directories where resources could be stored.
170 */
171 public Collection<String> getAllPossiblePreferenceDirs() {
172 LinkedList<String> locations = new LinkedList<String>();
173 locations.add(Main.pref.getPreferencesDir());
174 String s;
175 if ((s = System.getenv("JOSM_RESOURCES")) != null) {
176 if (!s.endsWith(File.separator)) {
177 s = s + File.separator;
178 }
179 locations.add(s);
180 }
181 if ((s = System.getProperty("josm.resources")) != null) {
182 if (!s.endsWith(File.separator)) {
183 s = s + File.separator;
184 }
185 locations.add(s);
186 }
187 String appdata = System.getenv("APPDATA");
188 if (System.getenv("ALLUSERSPROFILE") != null && appdata != null
189 && appdata.lastIndexOf(File.separator) != -1) {
190 appdata = appdata.substring(appdata.lastIndexOf(File.separator));
191 locations.add(new File(new File(System.getenv("ALLUSERSPROFILE"),
192 appdata), "JOSM").getPath());
193 }
194 locations.add("/usr/local/share/josm/");
195 locations.add("/usr/local/lib/josm/");
196 locations.add("/usr/share/josm/");
197 locations.add("/usr/lib/josm/");
198 return locations;
199 }
200
201 synchronized public boolean hasKey(final String key) {
202 return properties.containsKey(key);
203 }
204
205 /**
206 * Get settings value for a certain key.
207 * @param key the identifier for the setting
208 * @return "" if there is nothing set for the preference key,
209 * the corresponding value otherwise. The result is not null.
210 */
211 synchronized public String get(final String key) {
212 putDefault(key, null);
213 if (!properties.containsKey(key))
214 return "";
215 return properties.get(key);
216 }
217
218 /**
219 * Get settings value for a certain key and provide default a value.
220 * @param key the identifier for the setting
221 * @param def the default value. For each call of get() with a given key, the
222 * default value must be the same.
223 * @return the corresponding value if the property has been set before,
224 * def otherwise
225 */
226 synchronized public String get(final String key, final String def) {
227 putDefault(key, def);
228 final String prop = properties.get(key);
229 if (prop == null || prop.equals(""))
230 return def;
231 return prop;
232 }
233
234 synchronized public Map<String, String> getAllPrefix(final String prefix) {
235 final Map<String,String> all = new TreeMap<String,String>();
236 for (final Entry<String,String> e : properties.entrySet()) {
237 if (e.getKey().startsWith(prefix)) {
238 all.put(e.getKey(), e.getValue());
239 }
240 }
241 return all;
242 }
243
244 synchronized private Map<String, String> getAllPrefixDefault(final String prefix) {
245 final Map<String,String> all = new TreeMap<String,String>();
246 for (final Entry<String,String> e : defaults.entrySet()) {
247 if (e.getKey().startsWith(prefix)) {
248 all.put(e.getKey(), e.getValue());
249 }
250 }
251 return all;
252 }
253
254 synchronized public TreeMap<String, String> getAllColors() {
255 final TreeMap<String,String> all = new TreeMap<String,String>();
256 for (final Entry<String,String> e : defaults.entrySet()) {
257 if (e.getKey().startsWith("color.") && e.getValue() != null) {
258 all.put(e.getKey().substring(6), e.getValue());
259 }
260 }
261 for (final Entry<String,String> e : properties.entrySet()) {
262 if (e.getKey().startsWith("color.")) {
263 all.put(e.getKey().substring(6), e.getValue());
264 }
265 }
266 return all;
267 }
268
269 synchronized public Map<String, String> getDefaults() {
270 return defaults;
271 }
272
273 synchronized public void putDefault(final String key, final String def) {
274 if(!defaults.containsKey(key) || defaults.get(key) == null) {
275 defaults.put(key, def);
276 } else if(def != null && !defaults.get(key).equals(def)) {
277 System.out.println("Defaults for " + key + " differ: " + def + " != " + defaults.get(key));
278 }
279 }
280
281 synchronized public boolean getBoolean(final String key) {
282 putDefault(key, null);
283 return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : false;
284 }
285
286 synchronized public boolean getBoolean(final String key, final boolean def) {
287 putDefault(key, Boolean.toString(def));
288 return properties.containsKey(key) ? Boolean.parseBoolean(properties.get(key)) : def;
289 }
290
291 /**
292 * Set a value for a certain setting. The changed setting is saved
293 * to the preference file immediately. Due to caching mechanisms on modern
294 * operating systems and hardware, this shouldn't be a performance problem.
295 * @param key the unique identifier for the setting
296 * @param value the value of the setting. Can be null or "" wich both removes
297 * the key-value entry.
298 * @return if true, something has changed (i.e. value is different than before)
299 */
300 public boolean put(final String key, String value) {
301 boolean changed = false;
302 String oldValue = null;
303
304 synchronized (this) {
305 oldValue = properties.get(key);
306 if(value != null && value.length() == 0) {
307 value = null;
308 }
309 // value is the same as before - no need to save anything
310 boolean equalValue = oldValue != null && oldValue.equals(value);
311 // The setting was previously unset and we are supposed to put a
312 // value that equals the default value. This is not necessary because
313 // the default value is the same throughout josm. In addition we like
314 // to have the possibility to change the default value from version
315 // to version, which would not work if we wrote it to the preference file.
316 boolean unsetIsDefault = oldValue == null && (value == null || value.equals(defaults.get(key)));
317
318 if (!(equalValue || unsetIsDefault)) {
319 if (value == null) {
320 properties.remove(key);
321 } else {
322 properties.put(key, value);
323 }
324 try {
325 save();
326 } catch(IOException e){
327 System.out.println(tr("Warning: failed to persist preferences to ''{0}''", getPreferenceFile().getAbsoluteFile()));
328 }
329 changed = true;
330 }
331 }
332 if (changed) {
333 // Call outside of synchronized section in case some listener wait for other thread that wait for preference lock
334 firePreferenceChanged(key, oldValue, value);
335 }
336 return changed;
337 }
338
339 public boolean put(final String key, final boolean value) {
340 return put(key, Boolean.toString(value));
341 }
342
343 public boolean putInteger(final String key, final Integer value) {
344 return put(key, Integer.toString(value));
345 }
346
347 public boolean putDouble(final String key, final Double value) {
348 return put(key, Double.toString(value));
349 }
350
351 public boolean putLong(final String key, final Long value) {
352 return put(key, Long.toString(value));
353 }
354
355 /**
356 * Called after every put. In case of a problem, do nothing but output the error
357 * in log.
358 */
359 public void save() throws IOException {
360 /* currently unused, but may help to fix configuration issues in future */
361 putInteger("josm.version", Version.getInstance().getVersion());
362
363 updateSystemProperties();
364 if(Main.applet)
365 return;
366 File prefFile = new File(getPreferencesDirFile(), "preferences");
367
368 // Backup old preferences if there are old preferences
369 if(prefFile.exists()) {
370 copyFile(prefFile, new File(prefFile + "_backup"));
371 }
372
373 final PrintWriter out = new PrintWriter(new OutputStreamWriter(
374 new FileOutputStream(prefFile + "_tmp"), "utf-8"), false);
375 for (final Entry<String, String> e : properties.entrySet()) {
376 String s = defaults.get(e.getKey());
377 /* don't save default values */
378 if(s == null || !s.equals(e.getValue())) {
379 out.println(e.getKey() + "=" + e.getValue());
380 }
381 }
382 out.close();
383
384 File tmpFile = new File(prefFile + "_tmp");
385 copyFile(tmpFile, prefFile);
386 tmpFile.delete();
387 }
388
389 /**
390 * Simple file copy function that will overwrite the target file
391 * Taken from http://www.rgagnon.com/javadetails/java-0064.html (CC-NC-BY-SA)
392 * @param in
393 * @param out
394 * @throws IOException
395 */
396 public static void copyFile(File in, File out) throws IOException {
397 FileChannel inChannel = new FileInputStream(in).getChannel();
398 FileChannel outChannel = new FileOutputStream(out).getChannel();
399 try {
400 inChannel.transferTo(0, inChannel.size(),
401 outChannel);
402 }
403 catch (IOException e) {
404 throw e;
405 }
406 finally {
407 if (inChannel != null) {
408 inChannel.close();
409 }
410 if (outChannel != null) {
411 outChannel.close();
412 }
413 }
414 }
415
416 public void load() throws IOException {
417 properties.clear();
418 if(!Main.applet) {
419 final BufferedReader in = new BufferedReader(new InputStreamReader(
420 new FileInputStream(getPreferencesDir()+"preferences"), "utf-8"));
421 int lineNumber = 0;
422 ArrayList<Integer> errLines = new ArrayList<Integer>();
423 for (String line = in.readLine(); line != null; line = in.readLine(), lineNumber++) {
424 final int i = line.indexOf('=');
425 if (i == -1 || i == 0) {
426 errLines.add(lineNumber);
427 continue;
428 }
429 String key = line.substring(0,i);
430 String value = line.substring(i+1);
431 if (!value.isEmpty()) {
432 properties.put(key, value);
433 }
434 }
435 if (!errLines.isEmpty())
436 throw new IOException(tr("Malformed config file at lines {0}", errLines));
437 }
438 updateSystemProperties();
439 }
440
441 public void init(boolean reset){
442 if(Main.applet)
443 return;
444 // get the preferences.
445 File prefDir = getPreferencesDirFile();
446 if (prefDir.exists()) {
447 if(!prefDir.isDirectory()) {
448 System.err.println(tr("Warning: Failed to initialize preferences. Preference directory ''{0}'' is not a directory.", prefDir.getAbsoluteFile()));
449 JOptionPane.showMessageDialog(
450 Main.parent,
451 tr("<html>Failed to initialize preferences.<br>Preference directory ''{0}'' is not a directory.</html>", prefDir.getAbsoluteFile()),
452 tr("Error"),
453 JOptionPane.ERROR_MESSAGE
454 );
455 return;
456 }
457 } else {
458 if (! prefDir.mkdirs()) {
459 System.err.println(tr("Warning: Failed to initialize preferences. Failed to create missing preference directory: {0}", prefDir.getAbsoluteFile()));
460 JOptionPane.showMessageDialog(
461 Main.parent,
462 tr("<html>Failed to initialize preferences.<br>Failed to create missing preference directory: {0}</html>",prefDir.getAbsoluteFile()),
463 tr("Error"),
464 JOptionPane.ERROR_MESSAGE
465 );
466 return;
467 }
468 }
469
470 File preferenceFile = getPreferenceFile();
471 try {
472 if (!preferenceFile.exists()) {
473 System.out.println(tr("Warning: Missing preference file ''{0}''. Creating a default preference file.", preferenceFile.getAbsoluteFile()));
474 resetToDefault();
475 save();
476 } else if (reset) {
477 System.out.println(tr("Warning: Replacing existing preference file ''{0}'' with default preference file.", preferenceFile.getAbsoluteFile()));
478 resetToDefault();
479 save();
480 }
481 } catch(IOException e) {
482 e.printStackTrace();
483 JOptionPane.showMessageDialog(
484 Main.parent,
485 tr("<html>Failed to initialize preferences.<br>Failed to reset preference file to default: {0}</html>",getPreferenceFile().getAbsoluteFile()),
486 tr("Error"),
487 JOptionPane.ERROR_MESSAGE
488 );
489 return;
490 }
491 try {
492 load();
493 } catch (IOException e) {
494 e.printStackTrace();
495 File backupFile = new File(prefDir,"preferences.bak");
496 JOptionPane.showMessageDialog(
497 Main.parent,
498 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()),
499 tr("Error"),
500 JOptionPane.ERROR_MESSAGE
501 );
502 preferenceFile.renameTo(backupFile);
503 try {
504 resetToDefault();
505 save();
506 } catch(IOException e1) {
507 e1.printStackTrace();
508 System.err.println(tr("Warning: Failed to initialize preferences.Failed to reset preference file to default: {0}", getPreferenceFile()));
509 }
510 }
511 }
512
513 public final void resetToDefault(){
514 properties.clear();
515 }
516
517 /**
518 * Convenience method for accessing colour preferences.
519 *
520 * @param colName name of the colour
521 * @param def default value
522 * @return a Color object for the configured colour, or the default value if none configured.
523 */
524 synchronized public Color getColor(String colName, Color def) {
525 return getColor(colName, null, def);
526 }
527
528 public Color getColor(ColorKey key) {
529 return getColor(key.getColorName(), key.getSpecialName(), key.getDefault());
530 }
531
532 /**
533 * Convenience method for accessing colour preferences.
534 *
535 * @param colName name of the colour
536 * @param specName name of the special colour settings
537 * @param def default value
538 * @return a Color object for the configured colour, or the default value if none configured.
539 */
540 synchronized public Color getColor(String colName, String specName, Color def) {
541 putDefault("color."+colName, ColorHelper.color2html(def));
542 String colStr = specName != null ? get("color."+specName) : "";
543 if(colStr.equals("")) {
544 colStr = get("color."+colName);
545 }
546 return colStr.equals("") ? def : ColorHelper.html2color(colStr);
547 }
548
549 synchronized public Color getDefaultColor(String colName) {
550 String colStr = defaults.get("color."+colName);
551 return colStr == null || "".equals(colStr) ? null : ColorHelper.html2color(colStr);
552 }
553
554 synchronized public boolean putColor(String colName, Color val) {
555 return put("color."+colName, val != null ? ColorHelper.color2html(val) : null);
556 }
557
558 synchronized public int getInteger(String key, int def) {
559 putDefault(key, Integer.toString(def));
560 String v = get(key);
561 if(null == v)
562 return def;
563
564 try {
565 return Integer.parseInt(v);
566 } catch(NumberFormatException e) {
567 // fall out
568 }
569 return def;
570 }
571
572 synchronized public long getLong(String key, long def) {
573 putDefault(key, Long.toString(def));
574 String v = get(key);
575 if(null == v)
576 return def;
577
578 try {
579 return Long.parseLong(v);
580 } catch(NumberFormatException e) {
581 // fall out
582 }
583 return def;
584 }
585
586 synchronized public double getDouble(String key, double def) {
587 putDefault(key, Double.toString(def));
588 String v = get(key);
589 if(null == v)
590 return def;
591
592 try {
593 return Double.parseDouble(v);
594 } catch(NumberFormatException e) {
595 // fall out
596 }
597 return def;
598 }
599
600 synchronized public double getDouble(String key, String def) {
601 putDefault(key, def);
602 String v = get(key);
603 if(v != null && v.length() != 0) {
604 try { return Double.parseDouble(v); } catch(NumberFormatException e) {}
605 }
606 try { return Double.parseDouble(def); } catch(NumberFormatException e) {}
607 return 0.0;
608 }
609
610 synchronized public String getCollectionAsString(final String key) {
611 String s = get(key);
612 if(s != null && s.length() != 0) {
613 s = s.replaceAll("\u001e",",");
614 }
615 return s;
616 }
617
618 public boolean isCollection(String key, boolean def) {
619 String s = get(key);
620 if (s != null && s.length() != 0)
621 return s.indexOf("\u001e") >= 0;
622 else
623 return def;
624 }
625
626 /**
627 * Get a list of values for a certain key
628 * @param key the identifier for the setting
629 * @param def the default value.
630 * @return the corresponding value if the property has been set before,
631 * def otherwise
632 */
633 synchronized public Collection<String> getCollection(String key, Collection<String> def) {
634 putCollectionDefault(key, def);
635 String s = get(key);
636 if(s != null && s.length() != 0)
637 return Arrays.asList(s.split("\u001e", -1));
638 return def;
639 }
640
641 /**
642 * Get a list of values for a certain key
643 * @param key the identifier for the setting
644 * @return the corresponding value if the property has been set before,
645 * an empty Collection otherwise.
646 */
647 synchronized public Collection<String> getCollection(String key) {
648 putCollectionDefault(key, null);
649 String s = get(key);
650 if (s != null && s.length() != 0)
651 return Arrays.asList(s.split("\u001e", -1));
652 return Collections.emptyList();
653 }
654
655 /* old style conversion, replace by above call after some transition time */
656 /* remove this function, when no more old-style preference collections in the code */
657 @Deprecated
658 synchronized public Collection<String> getCollectionOld(String key, String sep) {
659 putCollectionDefault(key, null);
660 String s = get(key);
661 if (s != null && s.length() != 0) {
662 if(!s.contains("\u001e") && s.contains(sep)) {
663 s = s.replace(sep, "\u001e");
664 put(key, s);
665 }
666 return Arrays.asList(s.split("\u001e", -1));
667 }
668 return Collections.emptyList();
669 }
670
671 synchronized public void removeFromCollection(String key, String value) {
672 List<String> a = new ArrayList<String>(getCollection(key, Collections.<String>emptyList()));
673 a.remove(value);
674 putCollection(key, a);
675 }
676
677 synchronized public boolean putCollection(String key, Collection<String> val) {
678 return put(key, Utils.join("\u001e", val));
679 }
680
681 synchronized private void putCollectionDefault(String key, Collection<String> val) {
682 putDefault(key, Utils.join("\u001e", val));
683 }
684
685 /**
686 * Used to read a 2-dimensional array of strings from the preference file.
687 * If not a single entry could be found, def is returned.
688 */
689 synchronized public Collection<Collection<String>> getArray(String key,
690 Collection<Collection<String>> def)
691 {
692 if(def != null)
693 putArrayDefault(key, def);
694 key += ".";
695 int num = 0;
696 Collection<Collection<String>> col = new LinkedList<Collection<String>>();
697 while(properties.containsKey(key+num)) {
698 col.add(getCollection(key+num++, null));
699 }
700 return num == 0 ? def : col;
701 }
702
703 synchronized public boolean putArray(String key, Collection<Collection<String>> val) {
704 boolean changed = false;
705 key += ".";
706 Collection<String> keys = getAllPrefix(key).keySet();
707 if(val != null) {
708 int num = 0;
709 for(Collection<String> c : val) {
710 keys.remove(key+num);
711 changed |= putCollection(key+num++, c);
712 }
713 }
714 int l = key.length();
715 for(String k : keys) {
716 try {
717 Integer.valueOf(k.substring(l));
718 changed |= put(k, null);
719 } catch(NumberFormatException e) {
720 /* everything which does not end with a number should not be deleted */
721 }
722 }
723 return changed;
724 }
725
726 synchronized private void putArrayDefault(String key, Collection<Collection<String>> val) {
727 key += ".";
728 Collection<String> keys = getAllPrefixDefault(key).keySet();
729 int num = 0;
730 for(Collection<String> c : val) {
731 keys.remove(key+num);
732 putCollectionDefault(key+num++, c);
733 }
734 int l = key.length();
735 for(String k : keys) {
736 try {
737 Integer.valueOf(k.substring(l));
738 defaults.remove(k);
739 } catch(Exception e) {
740 /* everything which does not end with a number should not be deleted */
741 }
742 }
743 }
744
745 @Retention(RetentionPolicy.RUNTIME) public @interface pref { }
746 @Retention(RetentionPolicy.RUNTIME) public @interface writeExplicitly { }
747
748 /**
749 * Get a list of hashes which are represented by a struct-like class.
750 * It reads lines of the form
751 * > key.0=prop:val \u001e prop:val \u001e ... \u001e prop:val
752 * > ...
753 * > key.N=prop:val \u001e prop:val \u001e ... \u001e prop:val
754 * Possible properties are given by fields of the class klass that have
755 * the @pref annotation.
756 * Default constructor is used to initialize the struct objects, properties
757 * then override some of these default values.
758 * @param key main preference key
759 * @param klass The struct class
760 * @return a list of objects of type T or an empty list if nothing was found
761 */
762 public <T> List<T> getListOfStructs(String key, Class<T> klass) {
763 List<T> r = getListOfStructs(key, null, klass);
764 if (r == null)
765 return Collections.emptyList();
766 else
767 return r;
768 }
769
770 /**
771 * same as above, but returns def if nothing was found
772 */
773 public <T> List<T> getListOfStructs(String key, Collection<T> def, Class<T> klass) {
774 Collection<Collection<String>> array =
775 getArray(key, def == null ? null : serializeListOfStructs(def, klass));
776 if (array == null)
777 return def == null ? null : new ArrayList<T>(def);
778 List<T> lst = new ArrayList<T>();
779 for (Collection<String> entries : array) {
780 T struct = deserializeStruct(entries, klass);
781 lst.add(struct);
782 }
783 return lst;
784 }
785
786 /**
787 * Save a list of hashes represented by a struct-like class.
788 * Considers only fields that have the @pref annotation.
789 * In addition it does not write fields with null values. (Thus they are cleared)
790 * Default values are given by the field values after default constructor has
791 * been called.
792 * Fields equal to the default value are not written unless the field has
793 * the @writeExplicitly annotation.
794 * @param key main preference key
795 * @param val the list that is supposed to be saved
796 * @param klass The struct class
797 * @return true if something has changed
798 */
799 public <T> boolean putListOfStructs(String key, Collection<T> val, Class<T> klass) {
800 return putArray(key, serializeListOfStructs(val, klass));
801 }
802
803 private <T> Collection<Collection<String>> serializeListOfStructs(Collection<T> l, Class<T> klass) {
804 if (l == null)
805 return null;
806 Collection<Collection<String>> vals = new ArrayList<Collection<String>>();
807 for (T struct : l) {
808 if (struct == null)
809 continue;
810 vals.add(serializeStruct(struct, klass));
811 }
812 return vals;
813 }
814
815 private <T> Collection<String> serializeStruct(T struct, Class<T> klass) {
816 T structPrototype;
817 try {
818 structPrototype = klass.newInstance();
819 } catch (InstantiationException ex) {
820 throw new RuntimeException();
821 } catch (IllegalAccessException ex) {
822 throw new RuntimeException();
823 }
824
825 Collection<String> hash = new ArrayList<String>();
826 for (Field f : klass.getDeclaredFields()) {
827 if (f.getAnnotation(pref.class) == null) {
828 continue;
829 }
830 f.setAccessible(true);
831 try {
832 Object fieldValue = f.get(struct);
833 Object defaultFieldValue = f.get(structPrototype);
834 if (fieldValue != null) {
835 if (f.getAnnotation(writeExplicitly.class) != null || !Utils.equal(fieldValue, defaultFieldValue)) {
836 hash.add(String.format("%s:%s", f.getName().replace("_", "-"), fieldValue.toString()));
837 }
838 }
839 } catch (IllegalArgumentException ex) {
840 throw new RuntimeException();
841 } catch (IllegalAccessException ex) {
842 throw new RuntimeException();
843 }
844 }
845 return hash;
846 }
847
848 private <T> T deserializeStruct(Collection<String> hash, Class<T> klass) {
849 T struct = null;
850 try {
851 struct = klass.newInstance();
852 } catch (InstantiationException ex) {
853 throw new RuntimeException();
854 } catch (IllegalAccessException ex) {
855 throw new RuntimeException();
856 }
857 for (String key_value : hash) {
858 final int i = key_value.indexOf(':');
859 if (i == -1 || i == 0) {
860 continue;
861 }
862 String key = key_value.substring(0,i);
863 String valueString = key_value.substring(i+1);
864
865 Object value = null;
866 Field f;
867 try {
868 f = klass.getDeclaredField(key.replace("-", "_"));
869 } catch (NoSuchFieldException ex) {
870 continue;
871 } catch (SecurityException ex) {
872 throw new RuntimeException();
873 }
874 if (f.getAnnotation(pref.class) == null) {
875 continue;
876 }
877 f.setAccessible(true);
878 if (f.getType() == Boolean.class || f.getType() == boolean.class) {
879 value = Boolean.parseBoolean(valueString);
880 } else if (f.getType() == Integer.class || f.getType() == int.class) {
881 try {
882 value = Integer.parseInt(valueString);
883 } catch (NumberFormatException nfe) {
884 continue;
885 }
886 } else if (f.getType() == String.class) {
887 value = valueString;
888 } else
889 throw new RuntimeException("unsupported preference primitive type");
890
891 try {
892 f.set(struct, value);
893 } catch (IllegalArgumentException ex) {
894 throw new AssertionError();
895 } catch (IllegalAccessException ex) {
896 throw new RuntimeException();
897 }
898 }
899 return struct;
900 }
901
902 /**
903 * Updates system properties with the current values in the preferences.
904 *
905 */
906 public void updateSystemProperties() {
907 Properties sysProp = System.getProperties();
908 sysProp.put("http.agent", Version.getInstance().getAgentString());
909 System.setProperties(sysProp);
910 }
911
912 /**
913 * The default plugin site
914 */
915 private final static String[] DEFAULT_PLUGIN_SITE = {
916 "http://josm.openstreetmap.de/plugin%<?plugins=>"};
917
918 /**
919 * Replies the collection of plugin site URLs from where plugin lists can be downloaded
920 *
921 * @return
922 */
923 public Collection<String> getPluginSites() {
924 return getCollection("pluginmanager.sites", Arrays.asList(DEFAULT_PLUGIN_SITE));
925 }
926
927 /**
928 * Sets the collection of plugin site URLs.
929 *
930 * @param sites the site URLs
931 */
932 public void setPluginSites(Collection<String> sites) {
933 putCollection("pluginmanager.sites", sites);
934 }
935
936 public static class XMLTag {
937 public String key;
938 public String value;
939 }
940 public static class XMLCollection {
941 public String key;
942 }
943 public static class XMLEntry {
944 public String value;
945 }
946 public void fromXML(Reader in) throws SAXException {
947 XmlObjectParser parser = new XmlObjectParser();
948 parser.map("tag", XMLTag.class);
949 parser.map("entry", XMLEntry.class);
950 parser.map("collection", XMLCollection.class);
951 parser.startWithValidation(in,
952 "http://josm.openstreetmap.de/preferences-1.0", "resource://data/preferences.xsd");
953 LinkedList<String> vals = new LinkedList<String>();
954 while(parser.hasNext()) {
955 Object o = parser.next();
956 if(o instanceof XMLTag) {
957 properties.put(((XMLTag)o).key, ((XMLTag)o).value);
958 } else if (o instanceof XMLEntry) {
959 vals.add(((XMLEntry)o).value);
960 } else if (o instanceof XMLCollection) {
961 properties.put(((XMLCollection)o).key, Utils.join("\u001e", vals));
962 vals = new LinkedList<String>();
963 }
964 }
965 }
966
967 public String toXML(boolean nopass) {
968 StringBuilder b = new StringBuilder(
969 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
970 "<preferences xmlns=\"http://josm.openstreetmap.de/preferences-1.0\">\n");
971 for (Entry<String, String> p : properties.entrySet()) {
972 if (nopass && p.getKey().equals("osm-server.password")) {
973 continue; // do not store plain password.
974 }
975 String r = p.getValue();
976 if(r.contains("\u001e"))
977 {
978 b.append(" <collection key='");
979 b.append(XmlWriter.encode(p.getKey()));
980 b.append("'>\n");
981 for (String val : r.split("\u001e", -1))
982 {
983 b.append(" <entry value='");
984 b.append(XmlWriter.encode(val));
985 b.append("' />\n");
986 }
987 b.append(" </collection>\n");
988 }
989 else
990 {
991 b.append(" <tag key='");
992 b.append(XmlWriter.encode(p.getKey()));
993 b.append("' value='");
994 b.append(XmlWriter.encode(p.getValue()));
995 b.append("' />\n");
996 }
997 }
998 b.append("</preferences>");
999 return b.toString();
1000 }
1001}
Note: See TracBrowser for help on using the repository browser.