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

Last change on this file since 3908 was 3908, checked in by bastiK, 13 years ago

new preference type (list of structs). Should be more flexible when preference options are added and dropped for a list like features. (Not sure how far we get with this key=value approach, maybe it's time for xml preferences.) Fixes #5850 - Filter entries are mixed up

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