I18 changes
[debian/openrocket] / src / net / sf / openrocket / util / Prefs.java
1 package net.sf.openrocket.util;
2
3 import java.awt.Color;
4 import java.awt.Dimension;
5 import java.awt.Point;
6 import java.awt.Toolkit;
7 import java.io.File;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.MissingResourceException;
16 import java.util.Properties;
17 import java.util.Set;
18 import java.util.prefs.BackingStoreException;
19 import java.util.prefs.Preferences;
20
21 import net.sf.openrocket.arch.SystemInfo;
22 import net.sf.openrocket.database.Databases;
23 import net.sf.openrocket.document.Simulation;
24 import net.sf.openrocket.gui.main.ExceptionHandler;
25 import net.sf.openrocket.logging.LogHelper;
26 import net.sf.openrocket.material.Material;
27 import net.sf.openrocket.rocketcomponent.BodyComponent;
28 import net.sf.openrocket.rocketcomponent.FinSet;
29 import net.sf.openrocket.rocketcomponent.InternalComponent;
30 import net.sf.openrocket.rocketcomponent.LaunchLug;
31 import net.sf.openrocket.rocketcomponent.MassObject;
32 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
33 import net.sf.openrocket.rocketcomponent.Rocket;
34 import net.sf.openrocket.rocketcomponent.RocketComponent;
35 import net.sf.openrocket.simulation.FlightDataType;
36 import net.sf.openrocket.simulation.GUISimulationConditions;
37 import net.sf.openrocket.simulation.RK4SimulationStepper;
38 import net.sf.openrocket.startup.Application;
39 import net.sf.openrocket.unit.UnitGroup;
40
41
42 public class Prefs {
43         private static final LogHelper log = Application.getLogger();
44         
45         private static final String SPLIT_CHARACTER = "|";
46         
47
48         /**
49          * Whether to use the debug-node instead of the normal node.
50          */
51         private static final boolean DEBUG;
52         static {
53                 DEBUG = (System.getProperty("openrocket.debug.prefs") != null);
54         }
55         
56         /**
57          * Whether to clear all preferences at application startup.  This has an effect only
58          * if DEBUG is true.
59          */
60         private static final boolean CLEARPREFS = true;
61         
62         /**
63          * The node name to use in the Java preferences storage.
64          */
65         private static final String NODENAME = (DEBUG ? "OpenRocket-debug" : "OpenRocket");
66         
67         
68         /*
69          * Load property file only when necessary.
70          */
71         private static class BuildPropertyHolder {
72                 
73                 public static final Properties PROPERTIES;
74                 public static final String BUILD_VERSION;
75                 public static final String BUILD_SOURCE;
76                 public static final boolean DEFAULT_CHECK_UPDATES;
77                 
78                 static {
79                         try {
80                                 InputStream is = ClassLoader.getSystemResourceAsStream("build.properties");
81                                 if (is == null) {
82                                         throw new MissingResourceException(
83                                                         "build.properties not found, distribution built wrong" +
84                                                                         "   classpath:" + System.getProperty("java.class.path"),
85                                                         "build.properties", "build.version");
86                                 }
87                                 
88                                 PROPERTIES = new Properties();
89                                 PROPERTIES.load(is);
90                                 is.close();
91                                 
92                                 String version = PROPERTIES.getProperty("build.version");
93                                 if (version == null) {
94                                         throw new MissingResourceException(
95                                                         "build.version not found in property file",
96                                                         "build.properties", "build.version");
97                                 }
98                                 BUILD_VERSION = version.trim();
99                                 
100                                 BUILD_SOURCE = PROPERTIES.getProperty("build.source");
101                                 if (BUILD_SOURCE == null) {
102                                         throw new MissingResourceException(
103                                                         "build.source not found in property file",
104                                                         "build.properties", "build.source");
105                                 }
106                                 
107                                 String value = PROPERTIES.getProperty("build.checkupdates");
108                                 if (value != null)
109                                         DEFAULT_CHECK_UPDATES = Boolean.parseBoolean(value);
110                                 else
111                                         DEFAULT_CHECK_UPDATES = true;
112                                 
113                         } catch (IOException e) {
114                                 throw new MissingResourceException(
115                                                 "Error reading build.properties",
116                                                 "build.properties", "build.version");
117                         }
118                 }
119         }
120         
121         public static final String BODY_COMPONENT_INSERT_POSITION_KEY = "BodyComponentInsertPosition";
122         
123         public static final String USER_THRUST_CURVES_KEY = "UserThrustCurves";
124         
125         public static final String CONFIRM_DELETE_SIMULATION = "ConfirmDeleteSimulation";
126         
127         // Preferences related to data export
128         public static final String EXPORT_FIELD_SEPARATOR = "ExportFieldSeparator";
129         public static final String EXPORT_SIMULATION_COMMENT = "ExportSimulationComment";
130         public static final String EXPORT_FIELD_NAME_COMMENT = "ExportFieldDescriptionComment";
131         public static final String EXPORT_EVENT_COMMENTS = "ExportEventComments";
132         public static final String EXPORT_COMMENT_CHARACTER = "ExportCommentCharacter";
133         
134         public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
135         
136         private static final String CHECK_UPDATES = "CheckUpdates";
137         public static final String LAST_UPDATE = "LastUpdateVersion";
138         
139         public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
140         public static final String MOTOR_HIDE_SIMILAR = "MotorHideSimilar";
141         
142
143         // Node names
144         public static final String PREFERRED_THRUST_CURVE_MOTOR_NODE = "preferredThrustCurveMotors";
145         
146
147         /**
148          * Node to this application's preferences.
149          * @deprecated  Use the static methods instead.
150          */
151         @Deprecated
152         public static final Preferences NODE;
153         private static final Preferences PREFNODE;
154         
155
156         // Clear the preferences if debug mode and clearprefs is defined
157         static {
158                 Preferences root = Preferences.userRoot();
159                 if (DEBUG && CLEARPREFS) {
160                         try {
161                                 if (root.nodeExists(NODENAME)) {
162                                         root.node(NODENAME).removeNode();
163                                 }
164                         } catch (BackingStoreException e) {
165                                 throw new BugException("Unable to clear preference node", e);
166                         }
167                 }
168                 PREFNODE = root.node(NODENAME);
169                 NODE = PREFNODE;
170         }
171         
172
173
174
175         /////////  Default component attributes
176         
177         private static final HashMap<Class<?>, String> DEFAULT_COLORS =
178                         new HashMap<Class<?>, String>();
179         static {
180                 DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
181                 DEFAULT_COLORS.put(FinSet.class, "0,0,200");
182                 DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
183                 DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
184                 DEFAULT_COLORS.put(MassObject.class, "0,0,0");
185                 DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
186         }
187         
188
189         private static final HashMap<Class<?>, String> DEFAULT_LINE_STYLES =
190                         new HashMap<Class<?>, String>();
191         static {
192                 DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
193                 DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
194         }
195         
196         
197         /*
198          * Within a holder class so they will load only when needed.
199          */
200         private static class DefaultMaterialHolder {
201                 private static final Material DEFAULT_LINE_MATERIAL =
202                                 Databases.findMaterial(Material.Type.LINE, "Elastic cord (round 2mm, 1/16 in)",
203                                                 0.0018, false);
204                 private static final Material DEFAULT_SURFACE_MATERIAL =
205                                 Databases.findMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, false);
206                 private static final Material DEFAULT_BULK_MATERIAL =
207                                 Databases.findMaterial(Material.Type.BULK, "Cardboard", 680, false);
208         }
209         
210         //////////////////////
211         
212
213         /**
214          * Return the OpenRocket version number.
215          */
216         public static String getVersion() {
217                 return BuildPropertyHolder.BUILD_VERSION;
218         }
219         
220         
221         /**
222          * Return the OpenRocket build source (e.g. "default" or "Debian")
223          */
224         public static String getBuildSource() {
225                 return BuildPropertyHolder.BUILD_SOURCE;
226         }
227         
228         
229         /**
230          * Return the OpenRocket unique ID.
231          * 
232          * @return      a random ID string that stays constant between OpenRocket executions
233          */
234         public static String getUniqueID() {
235                 String id = PREFNODE.get("id", null);
236                 if (id == null) {
237                         id = UniqueID.uuid();
238                         PREFNODE.put("id", id);
239                 }
240                 return id;
241         }
242         
243         
244
245         /**
246          * Store the current OpenRocket version into the preferences to allow for preferences migration.
247          */
248         private static void storeVersion() {
249                 PREFNODE.put("OpenRocketVersion", getVersion());
250         }
251         
252         
253         /**
254          * Returns a limited-range integer value from the preferences.  If the value 
255          * in the preferences is negative or greater than max, then the default value 
256          * is returned.
257          * 
258          * @param key  The preference to retrieve.
259          * @param max  Maximum allowed value for the choice.
260          * @param def  Default value.
261          * @return   The preference value.
262          */
263         public static int getChoise(String key, int max, int def) {
264                 int v = PREFNODE.getInt(key, def);
265                 if ((v < 0) || (v > max))
266                         return def;
267                 return v;
268         }
269         
270         
271         /**
272          * Helper method that puts an integer choice value into the preferences.
273          * 
274          * @param key     the preference key.
275          * @param value   the value to store.
276          */
277         public static void putChoise(String key, int value) {
278                 PREFNODE.putInt(key, value);
279                 storeVersion();
280         }
281         
282         
283         /**
284          * Return a string preference.
285          * 
286          * @param key   the preference key.
287          * @param def   the default if no preference is stored
288          * @return              the preference value
289          */
290         public static String getString(String key, String def) {
291                 return PREFNODE.get(key, def);
292         }
293         
294         /**
295          * Set a string preference.
296          * 
297          * @param key           the preference key
298          * @param value         the value to set, or <code>null</code> to remove the key
299          */
300         public static void putString(String key, String value) {
301                 if (value == null) {
302                         PREFNODE.remove(key);
303                         return;
304                 }
305                 PREFNODE.put(key, value);
306                 storeVersion();
307         }
308         
309         /**
310          * Return a boolean preference.
311          * 
312          * @param key   the preference key
313          * @param def   the default if no preference is stored
314          * @return              the preference value
315          */
316         public static boolean getBoolean(String key, boolean def) {
317                 return PREFNODE.getBoolean(key, def);
318         }
319         
320         /**
321          * Set a boolean preference.
322          * 
323          * @param key           the preference key
324          * @param value         the value to set
325          */
326         public static void putBoolean(String key, boolean value) {
327                 PREFNODE.putBoolean(key, value);
328                 storeVersion();
329         }
330         
331         
332         /**
333          * Return a preferences object for the specified node name.
334          * 
335          * @param nodeName      the node name
336          * @return                      the preferences object for that node
337          */
338         public static Preferences getNode(String nodeName) {
339                 return PREFNODE.node(nodeName);
340         }
341         
342         
343         //////////////////
344         
345
346
347
348         public static boolean getCheckUpdates() {
349                 return PREFNODE.getBoolean(CHECK_UPDATES, BuildPropertyHolder.DEFAULT_CHECK_UPDATES);
350         }
351         
352         public static void setCheckUpdates(boolean check) {
353                 PREFNODE.putBoolean(CHECK_UPDATES, check);
354                 storeVersion();
355         }
356         
357         public static File getDefaultDirectory() {
358                 String file = PREFNODE.get("defaultDirectory", null);
359                 if (file == null)
360                         return null;
361                 return new File(file);
362         }
363         
364         public static void setDefaultDirectory(File dir) {
365                 String d;
366                 if (dir == null) {
367                         d = null;
368                 } else {
369                         d = dir.getAbsolutePath();
370                 }
371                 PREFNODE.put("defaultDirectory", d);
372                 storeVersion();
373         }
374         
375         
376         /**
377          * Return a list of files/directories to be loaded as custom thrust curves.
378          * <p>
379          * If this property has not been set, the directory "ThrustCurves" in the user
380          * application directory will be used.  The directory will be created if it does not
381          * exist.
382          * 
383          * @return      a list of files to load as thrust curves.
384          */
385         public static List<File> getUserThrustCurveFiles() {
386                 List<File> list = new ArrayList<File>();
387                 
388                 String files = getString(USER_THRUST_CURVES_KEY, null);
389                 if (files == null) {
390                         // Default to application directory
391                         File tcdir = getDefaultUserThrustCurveFile();
392                         if (!tcdir.isDirectory()) {
393                                 tcdir.mkdirs();
394                         }
395                         list.add(tcdir);
396                 } else {
397                         for (String file : files.split("\\" + SPLIT_CHARACTER)) {
398                                 file = file.trim();
399                                 if (file.length() > 0) {
400                                         list.add(new File(file));
401                                 }
402                         }
403                 }
404                 
405                 return list;
406         }
407         
408         public static File getDefaultUserThrustCurveFile() {
409                 File appdir = SystemInfo.getUserApplicationDirectory();
410                 File tcdir = new File(appdir, "ThrustCurves");
411                 return tcdir;
412         }
413         
414         
415         /**
416          * Set the list of files/directories to be loaded as custom thrust curves.
417          * 
418          * @param files         the files to load, or <code>null</code> to reset to default value.
419          */
420         public static void setUserThrustCurveFiles(List<File> files) {
421                 if (files == null) {
422                         putString(USER_THRUST_CURVES_KEY, null);
423                         return;
424                 }
425                 
426                 String str = "";
427                 
428                 for (File file : files) {
429                         if (str.length() > 0) {
430                                 str += SPLIT_CHARACTER;
431                         }
432                         str += file.getAbsolutePath();
433                 }
434                 putString(USER_THRUST_CURVES_KEY, str);
435         }
436         
437         
438
439
440
441         public static Color getDefaultColor(Class<? extends RocketComponent> c) {
442                 String color = get("componentColors", c, DEFAULT_COLORS);
443                 if (color == null)
444                         return Color.BLACK;
445                 
446                 String[] rgb = color.split(",");
447                 if (rgb.length == 3) {
448                         try {
449                                 int red = MathUtil.clamp(Integer.parseInt(rgb[0]), 0, 255);
450                                 int green = MathUtil.clamp(Integer.parseInt(rgb[1]), 0, 255);
451                                 int blue = MathUtil.clamp(Integer.parseInt(rgb[2]), 0, 255);
452                                 return new Color(red, green, blue);
453                         } catch (NumberFormatException ignore) {
454                         }
455                 }
456                 
457                 return Color.BLACK;
458         }
459         
460         public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
461                 if (color == null)
462                         return;
463                 String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
464                 set("componentColors", c, string);
465         }
466         
467         public static Color getMotorBorderColor() {
468                 // TODO: MEDIUM:  Motor color (settable?)
469                 return new Color(0, 0, 0, 200);
470         }
471         
472         
473         public static Color getMotorFillColor() {
474                 // TODO: MEDIUM:  Motor fill color (settable?)
475                 return new Color(0, 0, 0, 100);
476         }
477         
478         
479         public static LineStyle getDefaultLineStyle(Class<? extends RocketComponent> c) {
480                 String value = get("componentStyle", c, DEFAULT_LINE_STYLES);
481                 try {
482                         return LineStyle.valueOf(value);
483                 } catch (Exception e) {
484                         return LineStyle.SOLID;
485                 }
486         }
487         
488         public static void setDefaultLineStyle(Class<? extends RocketComponent> c,
489                         LineStyle style) {
490                 if (style == null)
491                         return;
492                 set("componentStyle", c, style.name());
493         }
494         
495         
496         /**
497          * Return the DPI setting of the monitor.  This is either the setting provided
498          * by the system or a user-specified DPI setting.
499          * 
500          * @return    the DPI setting to use.
501          */
502         public static double getDPI() {
503                 int dpi = PREFNODE.getInt("DPI", 0); // Tenths of a dpi
504                 
505                 if (dpi < 10) {
506                         dpi = Toolkit.getDefaultToolkit().getScreenResolution() * 10;
507                 }
508                 if (dpi < 10)
509                         dpi = 960;
510                 
511                 return (dpi) / 10.0;
512         }
513         
514         
515         public static double getDefaultMach() {
516                 // TODO: HIGH: implement custom default mach number
517                 return 0.3;
518         }
519         
520         
521
522
523         public static Material getDefaultComponentMaterial(
524                         Class<? extends RocketComponent> componentClass,
525                         Material.Type type) {
526                 
527                 String material = get("componentMaterials", componentClass, null);
528                 if (material != null) {
529                         try {
530                                 Material m = Material.fromStorableString(material, false);
531                                 if (m.getType() == type)
532                                         return m;
533                         } catch (IllegalArgumentException ignore) {
534                         }
535                 }
536                 
537                 switch (type) {
538                 case LINE:
539                         return DefaultMaterialHolder.DEFAULT_LINE_MATERIAL;
540                 case SURFACE:
541                         return DefaultMaterialHolder.DEFAULT_SURFACE_MATERIAL;
542                 case BULK:
543                         return DefaultMaterialHolder.DEFAULT_BULK_MATERIAL;
544                 }
545                 throw new IllegalArgumentException("Unknown material type: " + type);
546         }
547         
548         public static void setDefaultComponentMaterial(
549                         Class<? extends RocketComponent> componentClass, Material material) {
550                 
551                 set("componentMaterials", componentClass,
552                                 material == null ? null : material.toStorableString());
553         }
554         
555         
556         public static int getMaxThreadCount() {
557                 return Runtime.getRuntime().availableProcessors();
558         }
559         
560         
561         /**
562          * Return whether to use additional safety code checks.
563          */
564         public static boolean useSafetyChecks() {
565                 // Currently default to false unless openrocket.debug.safetycheck is defined
566                 String s = System.getProperty("openrocket.debug.safetycheck");
567                 if (s != null && !(s.equalsIgnoreCase("false") || s.equalsIgnoreCase("off"))) {
568                         return true;
569                 }
570                 return false;
571         }
572         
573         
574         public static Point getWindowPosition(Class<?> c) {
575                 int x, y;
576                 String pref = PREFNODE.node("windows").get("position." + c.getCanonicalName(), null);
577                 
578                 if (pref == null)
579                         return null;
580                 
581                 if (pref.indexOf(',') < 0)
582                         return null;
583                 
584                 try {
585                         x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
586                         y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
587                 } catch (NumberFormatException e) {
588                         return null;
589                 }
590                 return new Point(x, y);
591         }
592         
593         public static void setWindowPosition(Class<?> c, Point p) {
594                 PREFNODE.node("windows").put("position." + c.getCanonicalName(), "" + p.x + "," + p.y);
595                 storeVersion();
596         }
597         
598         
599
600
601         public static Dimension getWindowSize(Class<?> c) {
602                 int x, y;
603                 String pref = PREFNODE.node("windows").get("size." + c.getCanonicalName(), null);
604                 
605                 if (pref == null)
606                         return null;
607                 
608                 if (pref.indexOf(',') < 0)
609                         return null;
610                 
611                 try {
612                         x = Integer.parseInt(pref.substring(0, pref.indexOf(',')));
613                         y = Integer.parseInt(pref.substring(pref.indexOf(',') + 1));
614                 } catch (NumberFormatException e) {
615                         return null;
616                 }
617                 return new Dimension(x, y);
618         }
619         
620         public static void setWindowSize(Class<?> c, Dimension d) {
621                 PREFNODE.node("windows").put("size." + c.getCanonicalName(), "" + d.width + "," + d.height);
622                 storeVersion();
623         }
624         
625         
626         ////  Background flight data computation
627         
628         public static boolean computeFlightInBackground() {
629                 return PREFNODE.getBoolean("backgroundFlight", true);
630         }
631         
632         public static Simulation getBackgroundSimulation(Rocket rocket) {
633                 Simulation s = new Simulation(rocket);
634                 GUISimulationConditions cond = s.getConditions();
635                 
636                 cond.setTimeStep(RK4SimulationStepper.RECOMMENDED_TIME_STEP * 2);
637                 cond.setWindSpeedAverage(1.0);
638                 cond.setWindSpeedDeviation(0.1);
639                 cond.setLaunchRodLength(5);
640                 return s;
641         }
642         
643         
644
645         /////////  Export variables
646         
647         public static boolean isExportSelected(FlightDataType type) {
648                 Preferences prefs = PREFNODE.node("exports");
649                 return prefs.getBoolean(type.getName(), false);
650         }
651         
652         public static void setExportSelected(FlightDataType type, boolean selected) {
653                 Preferences prefs = PREFNODE.node("exports");
654                 prefs.putBoolean(type.getName(), selected);
655         }
656         
657         
658
659         /////////  Default unit storage
660         
661         public static void loadDefaultUnits() {
662                 Preferences prefs = PREFNODE.node("units");
663                 try {
664                         
665                         for (String key : prefs.keys()) {
666                                 UnitGroup group = UnitGroup.UNITS.get(key);
667                                 if (group == null)
668                                         continue;
669                                 
670                                 try {
671                                         group.setDefaultUnit(prefs.get(key, null));
672                                 } catch (IllegalArgumentException ignore) {
673                                 }
674                         }
675                         
676                 } catch (BackingStoreException e) {
677                         ExceptionHandler.handleErrorCondition(e);
678                 }
679         }
680         
681         public static void storeDefaultUnits() {
682                 Preferences prefs = PREFNODE.node("units");
683                 
684                 for (String key : UnitGroup.UNITS.keySet()) {
685                         UnitGroup group = UnitGroup.UNITS.get(key);
686                         if (group == null || group.getUnitCount() < 2)
687                                 continue;
688                         
689                         prefs.put(key, group.getDefaultUnit().getUnit());
690                 }
691         }
692         
693         
694
695         ////  Material storage
696         
697
698         /**
699          * Add a user-defined material to the preferences.  The preferences are
700          * first checked for an existing material matching the provided one using
701          * {@link Material#equals(Object)}.
702          * 
703          * @param m             the material to add.
704          */
705         public static void addUserMaterial(Material m) {
706                 Preferences prefs = PREFNODE.node("userMaterials");
707                 
708
709                 // Check whether material already exists
710                 if (getUserMaterials().contains(m)) {
711                         return;
712                 }
713                 
714                 // Add material using next free key (key is not used when loading)
715                 String mat = m.toStorableString();
716                 for (int i = 0;; i++) {
717                         String key = "material" + i;
718                         if (prefs.get(key, null) == null) {
719                                 prefs.put(key, mat);
720                                 return;
721                         }
722                 }
723         }
724         
725         
726         /**
727          * Remove a user-defined material from the preferences.  The matching is performed
728          * using {@link Material#equals(Object)}.
729          * 
730          * @param m             the material to remove.
731          */
732         public static void removeUserMaterial(Material m) {
733                 Preferences prefs = PREFNODE.node("userMaterials");
734                 
735                 try {
736                         
737                         // Iterate through materials and remove all keys with a matching material
738                         for (String key : prefs.keys()) {
739                                 String value = prefs.get(key, null);
740                                 try {
741                                         
742                                         Material existing = Material.fromStorableString(value, true);
743                                         if (existing.equals(m)) {
744                                                 prefs.remove(key);
745                                         }
746                                         
747                                 } catch (IllegalArgumentException ignore) {
748                                 }
749                                 
750                         }
751                         
752                 } catch (BackingStoreException e) {
753                         throw new IllegalStateException("Cannot read preferences!", e);
754                 }
755         }
756         
757         
758         /**
759          * Return a set of all user-defined materials in the preferences.  The materials
760          * are created marked as user-defined.
761          * 
762          * @return      a set of all user-defined materials.
763          */
764         public static Set<Material> getUserMaterials() {
765                 Preferences prefs = PREFNODE.node("userMaterials");
766                 
767                 HashSet<Material> materials = new HashSet<Material>();
768                 try {
769                         
770                         for (String key : prefs.keys()) {
771                                 String value = prefs.get(key, null);
772                                 try {
773                                         
774                                         Material m = Material.fromStorableString(value, true);
775                                         materials.add(m);
776                                         
777                                 } catch (IllegalArgumentException e) {
778                                         log.warn("Illegal material string " + value);
779                                 }
780                                 
781                         }
782                         
783                 } catch (BackingStoreException e) {
784                         throw new IllegalStateException("Cannot read preferences!", e);
785                 }
786                 
787                 return materials;
788         }
789         
790         
791         ////  Helper methods
792         
793         private static String get(String directory,
794                         Class<? extends RocketComponent> componentClass,
795                         Map<Class<?>, String> defaultMap) {
796                 
797                 // Search preferences
798                 Class<?> c = componentClass;
799                 Preferences prefs = PREFNODE.node(directory);
800                 while (c != null && RocketComponent.class.isAssignableFrom(c)) {
801                         String value = prefs.get(c.getSimpleName(), null);
802                         if (value != null)
803                                 return value;
804                         c = c.getSuperclass();
805                 }
806                 
807                 if (defaultMap == null)
808                         return null;
809                 
810                 // Search defaults
811                 c = componentClass;
812                 while (RocketComponent.class.isAssignableFrom(c)) {
813                         String value = defaultMap.get(c);
814                         if (value != null)
815                                 return value;
816                         c = c.getSuperclass();
817                 }
818                 
819                 return null;
820         }
821         
822         
823         private static void set(String directory, Class<? extends RocketComponent> componentClass,
824                         String value) {
825                 Preferences prefs = PREFNODE.node(directory);
826                 if (value == null)
827                         prefs.remove(componentClass.getSimpleName());
828                 else
829                         prefs.put(componentClass.getSimpleName(), value);
830                 storeVersion();
831         }
832         
833 }