Author: nextgens
Date: 2006-08-13 22:14:40 +0000 (Sun, 13 Aug 2006)
New Revision: 10071

Added:
   trunk/apps/blueBunny/src/freenet/config/
   trunk/apps/blueBunny/src/freenet/config/BooleanCallback.java
   trunk/apps/blueBunny/src/freenet/config/BooleanOption.java
   trunk/apps/blueBunny/src/freenet/config/Config.java
   trunk/apps/blueBunny/src/freenet/config/FilePersistentConfig.java
   trunk/apps/blueBunny/src/freenet/config/IntCallback.java
   trunk/apps/blueBunny/src/freenet/config/IntOption.java
   trunk/apps/blueBunny/src/freenet/config/InvalidConfigValueException.java
   trunk/apps/blueBunny/src/freenet/config/LongCallback.java
   trunk/apps/blueBunny/src/freenet/config/LongOption.java
   trunk/apps/blueBunny/src/freenet/config/Option.java
   trunk/apps/blueBunny/src/freenet/config/OptionFormatException.java
   trunk/apps/blueBunny/src/freenet/config/ShortCallback.java
   trunk/apps/blueBunny/src/freenet/config/ShortOption.java
   trunk/apps/blueBunny/src/freenet/config/StringArrCallback.java
   trunk/apps/blueBunny/src/freenet/config/StringArrOption.java
   trunk/apps/blueBunny/src/freenet/config/StringCallback.java
   trunk/apps/blueBunny/src/freenet/config/StringOption.java
   trunk/apps/blueBunny/src/freenet/config/SubConfig.java
   trunk/apps/blueBunny/src/freenet/support/
   trunk/apps/blueBunny/src/freenet/support/Fields.java
   trunk/apps/blueBunny/src/freenet/support/LineReader.java
   trunk/apps/blueBunny/src/freenet/support/SimpleFieldSet.java
   trunk/apps/blueBunny/src/freenet/support/URLDecoder.java
   trunk/apps/blueBunny/src/freenet/support/URLEncodedFormatException.java
   trunk/apps/blueBunny/src/freenet/support/URLEncoder.java
   trunk/apps/blueBunny/src/freenet/support/io/
   trunk/apps/blueBunny/src/freenet/support/io/LineReader.java
   trunk/apps/blueBunny/src/freenet/support/io/LineReadingInputStream.java
   trunk/apps/blueBunny/src/freenet/support/io/TooLongException.java
Modified:
   trunk/apps/blueBunny/src/freenet/systray/Systray.java
Log:
blueBunny: import some code from the node to handle the configuration properly

Added: trunk/apps/blueBunny/src/freenet/config/BooleanCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/BooleanCallback.java        
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/BooleanCallback.java        
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of integer type changes.
+ * Also reports the current value.
+ */
+public interface BooleanCallback {
+       
+       /**
+        * Get the current, used value of the config variable.
+        */
+       boolean get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(boolean val) throws InvalidConfigValueException;
+
+}

Added: trunk/apps/blueBunny/src/freenet/config/BooleanOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/BooleanOption.java  2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/BooleanOption.java  2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,52 @@
+package freenet.config;
+
+public class BooleanOption extends Option {
+       
+       final boolean defaultValue;
+       final BooleanCallback cb;
+       private boolean currentValue;
+       
+       public BooleanOption(SubConfig conf, String optionName, boolean 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
BooleanCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public boolean getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               if(val.equalsIgnoreCase("true") || val.equalsIgnoreCase("yes")) 
{
+                       set(true);
+               } else if(val.equalsIgnoreCase("false") || 
val.equalsIgnoreCase("no")) {
+                       set(false);
+               } else
+                       throw new OptionFormatException("Unrecognized boolean: 
"+val);
+       }
+       
+       public void set(boolean b) throws InvalidConfigValueException {
+               cb.set(b);
+               currentValue = b;
+       }
+       
+       public String getValueString() {
+               return Boolean.toString(getValue());
+       }
+
+       public void setInitialValue(String val) throws 
InvalidConfigValueException {
+               if(val.equalsIgnoreCase("true") || val.equalsIgnoreCase("yes")) 
{
+                       currentValue = true;
+               } else if(val.equalsIgnoreCase("false") || 
val.equalsIgnoreCase("no")) {
+                       currentValue = false;
+               } else
+                       throw new OptionFormatException("Unrecognized boolean: 
"+val);
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/Config.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/Config.java 2006-08-13 22:01:32 UTC 
(rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/Config.java 2006-08-13 22:14:40 UTC 
(rev 10071)
@@ -0,0 +1,48 @@
+package freenet.config;
+
+import java.util.HashMap;
+
+/** Global configuration object for a node. SubConfig's register here.
+ * Handles writing to a file etc.
+ */
+public class Config {
+
+       protected final HashMap configsByPrefix;
+       
+       public Config() {
+               configsByPrefix = new HashMap();
+       }
+       
+       public void register(SubConfig sc) {
+               synchronized(this) {
+                       if(configsByPrefix.containsKey(sc.prefix))
+                               throw new IllegalArgumentException("Already 
registered "+sc.prefix+": "+sc);
+                       configsByPrefix.put(sc.prefix, sc);
+               }
+       }
+       
+       /** Write current config to disk 
+        * @throws IOException */
+       public void store() {
+               // Do nothing
+       }
+
+       /** Finished initialization */
+       public void finishedInit() {
+               // Do nothing
+       }
+
+       public void onRegister(SubConfig config, Option o) {
+               // Do nothing
+       }
+
+       /** Fetch all the SubConfig's. Used by user-facing config thingies. */
+       public synchronized SubConfig[] getConfigs() {
+               return (SubConfig[]) configsByPrefix.values().toArray(new 
SubConfig[configsByPrefix.size()]);
+       }
+       
+       public synchronized SubConfig get(String subConfig){
+               return (SubConfig)configsByPrefix.get(subConfig);
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/FilePersistentConfig.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/FilePersistentConfig.java   
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/FilePersistentConfig.java   
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,160 @@
+package freenet.config;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Iterator;
+
+import freenet.support.SimpleFieldSet;
+import freenet.support.io.LineReadingInputStream;
+
+/**
+ * Global Config object which persists to a file.
+ * 
+ * Reads the config file into a SimpleFieldSet when created.
+ * During init, SubConfig's are registered, and fed the relevant parts of the 
SFS.
+ * Once initialization has finished, we check whether there are any options 
remaining.
+ * If so, we complain about them.
+ * And then we write the config file back out.
+ */
+public class FilePersistentConfig extends Config {
+
+       final File filename;
+       final File tempFilename;
+       private SimpleFieldSet origConfigFileContents;
+       
+       public FilePersistentConfig(File f) throws IOException {
+               this.filename = f;
+               this.tempFilename = new File(f.getPath()+".tmp");
+               boolean filenameExists = f.exists();
+               boolean tempFilenameExists = tempFilename.exists();
+               if(filenameExists && !filename.canWrite()) 
+                       System.err.println("Warning: Cannot write to config 
file: "+filename);
+               if(tempFilenameExists && !tempFilename.canWrite())
+                       System.err.println("Warning: Cannot write to config 
tempfile: "+tempFilename);
+               if(filenameExists) {
+                       if(f.canRead()) {
+                               try {
+                                       initialLoad(filename);
+                                       return;
+                               } catch (FileNotFoundException e) {
+                                       System.err.println("No config file 
found, creating new: "+f);
+                               } catch (EOFException e) {
+                                       System.err.println("Empty config file 
"+f);
+                               }
+                               // Other IOE's indicate a more serious problem.
+                       } else {
+                               throw new IOException("Cannot read config 
file");
+                       }
+               }
+               if(tempFilename.exists()) {
+                       if(tempFilename.canRead()) {
+                               try {
+                                       initialLoad(tempFilename);
+                               } catch (FileNotFoundException e) {
+                                       System.err.println("No config file 
found, creating new: "+f);
+                               } // Other IOE's indicate a more serious 
problem.
+                       } else {
+                               throw new IOException("Cannot read config 
file");
+                       }
+               } else {
+                       System.err.println("No config file found, creating new: 
"+f);
+               }
+       }
+
+       /** Load the config file into a SimpleFieldSet. 
+        * @throws IOException */
+       private void initialLoad(File toRead) throws IOException {
+               FileInputStream fis = new FileInputStream(toRead);
+               BufferedInputStream bis = new BufferedInputStream(fis);
+               try {
+                       LineReadingInputStream lis = new 
LineReadingInputStream(bis);
+                       // Config file is UTF-8 too!
+                       synchronized (this) {
+                               origConfigFileContents = new 
SimpleFieldSet(lis, 32768, 128, true, true);       
+                       }
+               } finally {
+                       try {
+                               fis.close();
+                       } catch (IOException e) {
+                               System.err.println("Could not close 
"+filename+": "+e);
+                               e.printStackTrace();
+                       }
+               }
+       }
+       
+       public void register(SubConfig sc) {
+               super.register(sc);
+       }
+       
+       /**
+        * Finished initialization. So any remaining options must be invalid.
+        */
+       public synchronized void finishedInit() {
+               if(origConfigFileContents == null) return;
+               Iterator i = origConfigFileContents.keyIterator();
+               while(i.hasNext()) {
+                       String key = (String) i.next();
+                       System.err.println("Unknown option: "+key+" 
(value="+origConfigFileContents.get(key)+")");
+               }
+       }
+       
+       public void store() {
+               try {
+                       innerStore();
+               } catch (IOException e) {
+                       String err = "Cannot store config: "+e;
+                       System.err.println(err);
+                       e.printStackTrace();
+               }
+       }
+       
+       public void innerStore() throws IOException {
+               SimpleFieldSet fs = exportFieldSet();
+               FileOutputStream fos = new FileOutputStream(tempFilename);
+               BufferedWriter bw = new BufferedWriter(new 
OutputStreamWriter(fos, "UTF-8"));
+               synchronized(this) {
+                       fs.writeTo(bw);
+               }
+               bw.close();
+               if(!tempFilename.renameTo(filename)) {
+                       filename.delete();
+                       tempFilename.renameTo(filename);
+               }
+       }
+
+       private synchronized SimpleFieldSet exportFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet();
+               SubConfig[] configs;
+               synchronized(this) {
+                       configs = (SubConfig[]) 
configsByPrefix.values().toArray(new SubConfig[configsByPrefix.size()]);
+               }
+               for(int i=0;i<configs.length;i++) {
+                       SimpleFieldSet scfs = configs[i].exportFieldSet();
+                       fs.put(configs[i].prefix, scfs);
+               }
+               return fs;
+       }
+       
+       public void onRegister(SubConfig config, Option o) {
+               String val, name;
+               synchronized(this) {
+                       if(origConfigFileContents == null) return;
+                       name = 
config.prefix+SimpleFieldSet.MULTI_LEVEL_CHAR+o.name;
+                       val = origConfigFileContents.get(name);
+                       origConfigFileContents.removeValue(name);
+                       if(val == null) return;
+               }
+               try {
+                       o.setInitialValue(val);
+               } catch (InvalidConfigValueException e) {
+                       System.err.println("Could not parse config option 
"+name+": "+e);
+               }
+       }
+}

Added: trunk/apps/blueBunny/src/freenet/config/IntCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/IntCallback.java    2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/IntCallback.java    2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of integer type changes.
+ * Also reports the current value.
+ */
+public interface IntCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       int get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(int val) throws InvalidConfigValueException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/IntOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/IntOption.java      2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/IntOption.java      2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,63 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+/** Integer config variable */
+public class IntOption extends Option {
+
+       final int defaultValue;
+       final IntCallback cb;
+       private int currentValue;
+       // Cache it mostly so that we can keep SI units
+       private String cachedStringValue;
+       
+       public IntOption(SubConfig conf, String optionName, int defaultValue, 
String defaultValueString,
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, IntCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+
+       public IntOption(SubConfig conf, String optionName, String 
defaultValueString,
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, IntCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = Fields.parseInt(defaultValueString);
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public int getValue() {
+               if(config.hasFinishedInitialization()) {
+                       int val = cb.get();
+                       if(currentValue != val) {
+                               currentValue = val;
+                               cachedStringValue = null;
+                       }
+               }
+               return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               int x = Fields.parseInt(val);
+               cb.set(x);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+       
+       public void setInitialValue(String val) {
+               int x = Fields.parseInt(val);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+
+       public String getValueString() {
+               if(cachedStringValue != null) return cachedStringValue;
+               return Integer.toString(getValue());
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/InvalidConfigValueException.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/InvalidConfigValueException.java    
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/InvalidConfigValueException.java    
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,17 @@
+package freenet.config;
+
+/**
+ * Thrown when the node refuses to set a config variable to a particular
+ * value because it is invalid. Just because this is not thrown does not
+ * necessarily mean that there are no problems with the value defined,
+ * it merely means that there are no immediately detectable problems with 
+ * it.
+ */
+public class InvalidConfigValueException extends Exception {
+       private static final long serialVersionUID = -1;
+
+       public InvalidConfigValueException(String msg) {
+               super(msg);
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/config/LongCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/LongCallback.java   2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/LongCallback.java   2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of long type changes.
+ * Also reports the current value.
+ */
+public interface LongCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       long get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(long val) throws InvalidConfigValueException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/LongOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/LongOption.java     2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/LongOption.java     2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,63 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+/** Long config variable */
+public class LongOption extends Option {
+
+       final long defaultValue;
+       final LongCallback cb;
+       private long currentValue;
+       // Cache it mostly so that we can keep SI units
+       private String cachedStringValue;
+
+       public LongOption(SubConfig conf, String optionName, long defaultValue, 
String defaultValueString, 
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, LongCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+       
+       public LongOption(SubConfig conf, String optionName, String 
defaultValueString, 
+                       int sortOrder, boolean expert, String shortDesc, String 
longDesc, LongCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = Fields.parseLong(defaultValueString);
+               this.cb = cb;
+               this.currentValue = defaultValue;
+               this.cachedStringValue = defaultValueString;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public long getValue() {
+               if(config.hasFinishedInitialization()) {
+                       long val = cb.get();
+                       if(currentValue != val) {
+                               currentValue = val;
+                               cachedStringValue = null;
+                       }
+               }
+               return currentValue;
+       }
+       
+       public void setValue(String val) throws InvalidConfigValueException {
+               long x = Fields.parseLong(val);
+               cb.set(x);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+       
+       public String getValueString() {
+               if(cachedStringValue != null) return cachedStringValue;
+               return Long.toString(getValue());
+       }
+
+       public void setInitialValue(String val) throws 
InvalidConfigValueException {
+               long x = Fields.parseLong(val);
+               cachedStringValue = val;
+               currentValue = x;
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/config/Option.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/Option.java 2006-08-13 22:01:32 UTC 
(rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/Option.java 2006-08-13 22:14:40 UTC 
(rev 10071)
@@ -0,0 +1,70 @@
+package freenet.config;
+
+/**
+ * A config option.
+ */
+public abstract class Option {
+
+       /** The parent SubConfig object */
+       final SubConfig config;
+       /** The option name */
+       final String name;
+       /** The sort order */
+       final int sortOrder;
+       /** Is this config variable expert-only? */
+       final boolean expert;
+       /** Short description of value e.g. "FCP port" */
+       final String shortDesc;
+       /** Long description of value e.g. "The TCP port to listen for FCP 
connections on" */
+       final String longDesc;
+       
+       Option(SubConfig config, String name, int sortOrder, boolean expert, 
String shortDesc, String longDesc) {
+               this.config = config;
+               this.name = name;
+               this.sortOrder = sortOrder;
+               this.expert = expert;
+               this.shortDesc = shortDesc;
+               this.longDesc = longDesc;
+       }
+
+       /**
+        * Set this option's current value to a string. Will call the callback. 
Does not care 
+        * whether the value of the option has changed.
+        */
+       public abstract void setValue(String val) throws 
InvalidConfigValueException;
+
+       /**
+        * Get the current value of the option as a string.
+        */
+       public abstract String getValueString();
+
+       /** Set to a value from the config file; this is not passed on to the 
callback, as we
+        * expect the client-side initialization to check the value. The 
callback is not valid
+        * until the client calls finishedInitialization().
+        * @throws InvalidConfigValueException 
+        */
+       public abstract void setInitialValue(String val) throws 
InvalidConfigValueException;
+
+       /**
+        * Call the callback with the current value of the option.
+        */
+       public void forceUpdate() throws InvalidConfigValueException {
+               setValue(getValueString());
+       }
+       
+       public String getName(){
+               return name;
+       }
+       
+       public String getShortDesc(){
+               return shortDesc;
+       }
+       
+       public String getLongDesc(){
+               return longDesc;
+       }
+       
+       public boolean isExpert(){
+               return expert;
+       }
+}

Added: trunk/apps/blueBunny/src/freenet/config/OptionFormatException.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/OptionFormatException.java  
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/OptionFormatException.java  
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,13 @@
+package freenet.config;
+
+/**
+ * Thrown when a format error occurs, and we cannot parse the string set into 
the appropriate
+ * type.
+ */
+public class OptionFormatException extends InvalidConfigValueException {
+       private static final long serialVersionUID = -1;
+       public OptionFormatException(String msg) {
+               super(msg);
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/config/ShortCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/ShortCallback.java  2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/ShortCallback.java  2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,22 @@
+package freenet.config;
+
+/**
+ * A callback to be called when a config value of short type changes.
+ * Also reports the current value.
+ */
+public interface ShortCallback {
+
+       /**
+        * Get the current, used value of the config variable.
+        */
+       short get();
+       
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(short val) throws InvalidConfigValueException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/ShortOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/ShortOption.java    2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/ShortOption.java    2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,42 @@
+package freenet.config;
+
+import freenet.support.Fields;
+
+public class ShortOption extends Option {
+       
+       final short defaultValue;
+       final ShortCallback cb;
+       private short currentValue;
+       
+       public ShortOption(SubConfig conf, String optionName, short 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
ShortCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public short getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+       
+       public void setValue(String val) throws InvalidConfigValueException {
+               short x = Fields.parseShort(val);
+               cb.set(x);
+               currentValue = x;
+       }
+
+       public String getValueString() {
+               return Short.toString(getValue());
+       }
+
+       public void setInitialValue(String val) throws 
InvalidConfigValueException {
+               short x = Fields.parseShort(val);
+               currentValue = x;
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/StringArrCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/StringArrCallback.java      
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/StringArrCallback.java      
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,19 @@
+package freenet.config;
+
+/** Callback (getter/setter) for a string config variable */
+public interface StringArrCallback {
+       
+       /**
+        * Get the current, used value of the config variable.
+        */
+       String get();
+
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(String val) throws InvalidConfigValueException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/StringArrOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/StringArrOption.java        
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/StringArrOption.java        
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,75 @@
+package freenet.config;
+
+import freenet.support.URLDecoder;
+import freenet.support.URLEncodedFormatException;
+import freenet.support.URLEncoder;
+
+public class StringArrOption extends Option {
+
+    private final String defaultValue;
+    private final StringArrCallback cb;
+       private String currentValue;
+       
+    public static final String delimiter = ";";
+       
+       public StringArrOption(SubConfig conf, String optionName, String 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
StringArrCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = (defaultValue==null)?"":defaultValue;
+               this.cb = cb;
+               this.currentValue = (defaultValue==null)?"":defaultValue;
+       }
+       
+       public StringArrOption(SubConfig conf, String optionName, String 
defaultValue[], int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
StringArrCallback cb) {
+               this(conf, optionName, arrayToString(defaultValue), sortOrder, 
expert, shortDesc, longDesc, cb);
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public String[] getValue() {
+               return getValueString().split(delimiter);
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               setInitialValue(val);
+               cb.set(this.currentValue);
+       }
+       
+       public String getValueString() {
+               if(config.hasFinishedInitialization())
+                       currentValue = cb.get();
+               return currentValue;
+       }
+       
+       public void setInitialValue(String val) throws 
InvalidConfigValueException {
+               this.currentValue = val;
+       }
+       
+       
+       public static String arrayToString(String[] arr) {
+               if (arr == null)
+                       return null;
+               StringBuffer sb = new StringBuffer();
+               for (int i = 0 ; i < arr.length ; i++)
+                       sb.append(arr[i] + delimiter);
+               return sb.toString();
+       }
+       
+       public static String encode(String s) {
+               return URLEncoder.encode(s);
+       }
+       
+       public static String decode(String s) {
+               try {
+                       return URLDecoder.decode(s);
+               } catch (URLEncodedFormatException e) {
+                       return null;
+               }
+       }
+
+       public String getDefaultValue() {
+               return defaultValue;
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/StringCallback.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/StringCallback.java 2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/StringCallback.java 2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,19 @@
+package freenet.config;
+
+/** Callback (getter/setter) for a string config variable */
+public interface StringCallback {
+       
+       /**
+        * Get the current, used value of the config variable.
+        */
+       String get();
+
+       /**
+        * Set the config variable to a new value.
+        * @param val The new value.
+        * @throws InvalidConfigOptionException If the new value is invalid for 
+        * this particular option.
+        */
+       void set(String val) throws InvalidConfigValueException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/StringOption.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/StringOption.java   2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/StringOption.java   2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,38 @@
+package freenet.config;
+
+public class StringOption extends Option {
+
+       final String defaultValue;
+       final StringCallback cb;
+       private String currentValue;
+       
+       public StringOption(SubConfig conf, String optionName, String 
defaultValue, int sortOrder, 
+                       boolean expert, String shortDesc, String longDesc, 
StringCallback cb) {
+               super(conf, optionName, sortOrder, expert, shortDesc, longDesc);
+               this.defaultValue = defaultValue;
+               this.cb = cb;
+               this.currentValue = defaultValue;
+       }
+       
+       /** Get the current value. This is the value in use if we have finished
+        * initialization, otherwise it is the value set at startup (possibly 
the default). */
+       public String getValue() {
+               if(config.hasFinishedInitialization())
+                       return currentValue = cb.get();
+               else return currentValue;
+       }
+
+       public void setValue(String val) throws InvalidConfigValueException {
+               cb.set(val);
+               this.currentValue = val;
+       }
+       
+       public String getValueString() {
+               return getValue();
+       }
+
+       public void setInitialValue(String val) throws 
InvalidConfigValueException {
+               this.currentValue = val;
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/config/SubConfig.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/config/SubConfig.java      2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/config/SubConfig.java      2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,209 @@
+package freenet.config;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import freenet.support.SimpleFieldSet;
+
+/**
+ * A specific configuration block.
+ */
+public class SubConfig {
+       
+       private final HashMap map;
+       public final Config config;
+       final String prefix;
+       private boolean hasInitialized;
+       
+       public SubConfig(String prefix, Config config) {
+               this.config = config;
+               this.prefix = prefix;
+               map = new HashMap();
+               hasInitialized = false;
+               config.register(this);
+       }
+       
+       /**
+        * Return all the options registered. Each includes its name.
+        * Used by e.g. webconfig.
+        */
+       public synchronized Option[] getOptions() {
+               return (Option[]) map.values().toArray(new Option[map.size()]);
+       }
+       
+       public synchronized Option getOption(String option){
+               return (Option)map.get(option);
+       }
+       
+       public void register(Option o) {
+               synchronized(this) {
+                       if(o.name.indexOf(SimpleFieldSet.MULTI_LEVEL_CHAR) != 
-1)
+                               throw new IllegalArgumentException("Option 
names must not contain "+SimpleFieldSet.MULTI_LEVEL_CHAR);
+                       if(map.containsKey(o.name))
+                               throw new IllegalArgumentException("Already 
registered: "+o.name+" on "+this);
+                       map.put(o.name, o);
+               }
+               config.onRegister(this, o);
+       }
+       
+       public void register(String optionName, int defaultValue, int sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
IntCallback cb) {
+               register(new IntOption(this, optionName, defaultValue, null, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, long defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
LongCallback cb) {
+               register(new LongOption(this, optionName, defaultValue, null, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValueString, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
IntCallback cb) {
+               register(new IntOption(this, optionName, defaultValueString, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValueString, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
LongCallback cb) {
+               register(new LongOption(this, optionName, defaultValueString, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, boolean defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
BooleanCallback cb) {
+               register(new BooleanOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
StringCallback cb) {
+               register(new StringOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, short defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
ShortCallback cb) {
+               register(new ShortOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public void register(String optionName, String[] defaultValue, int 
sortOrder,
+                       boolean expert, String shortDesc, String longDesc, 
StringArrCallback cb) {
+               register(new StringArrOption(this, optionName, defaultValue, 
sortOrder, expert, shortDesc, longDesc, cb));
+       }
+       
+       public int getInt(String optionName) {
+               IntOption o;
+               synchronized(this) {
+                       o = (IntOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public long getLong(String optionName) {
+               LongOption o;
+               synchronized(this) {
+                       o = (LongOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public boolean getBoolean(String optionName) {
+               BooleanOption o;
+               synchronized(this) {
+                       o = (BooleanOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public String getString(String optionName) {
+               StringOption o;
+               synchronized(this) {
+                       o = (StringOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       public String[] getStringArr(String optionName) {
+               StringArrOption o;
+               synchronized(this) {
+                       o = (StringArrOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+
+       public short getShort(String optionName) {
+               ShortOption o;
+               synchronized(this) {
+                       o = (ShortOption) map.get(optionName);
+               }
+               return o.getValue();
+       }
+       
+       /**
+        * Has the object we are attached to finished initialization?
+        */
+       public boolean hasFinishedInitialization() {
+               return hasInitialized;
+       }
+
+       /**
+        * Called when the object we are attached to has finished init.
+        * After this point, the callbacks are authoritative for values of
+        * config variables, and will be called when values are changed by
+        * the user.
+        */
+       public void finishedInitialization() {
+               hasInitialized = true;
+       }
+
+       /**
+        * Set options from a SimpleFieldSet. Once we process an option, we 
must remove it.
+        */
+       public void setOptions(SimpleFieldSet sfs) {
+               Set entrySet = map.entrySet();
+               Iterator i = entrySet.iterator();
+               while(i.hasNext()) {
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String key = (String) entry.getKey();
+                       Option o = (Option) entry.getValue();
+                       String val = sfs.get(key);
+                       if(val != null) {
+                               try {
+                                       o.setValue(val);
+                               } catch (InvalidConfigValueException e) {
+                                       String msg = "Invalid config value: 
"+prefix+SimpleFieldSet.MULTI_LEVEL_CHAR+key+" = "+val+" : error: "+e;
+                                       System.err.println(msg); // might be 
about logging?
+                               }
+                       }
+               }
+       }
+
+       public SimpleFieldSet exportFieldSet() {
+               SimpleFieldSet fs = new SimpleFieldSet();
+               Set entrySet = map.entrySet();
+               Iterator i = entrySet.iterator();
+               while(i.hasNext()) {
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String key = (String) entry.getKey();
+                       Option o = (Option) entry.getValue();
+                       fs.put(key, o.getValueString());
+               }
+               return fs;
+       }
+
+       /**
+        * Force an option to be updated even if it hasn't changed.
+        * @throws InvalidConfigValueException 
+        */
+       public void forceUpdate(String optionName) throws 
InvalidConfigValueException {
+               Option o = (Option) map.get(optionName);
+               o.forceUpdate();
+       }
+
+       public void set(String name, String value) throws 
InvalidConfigValueException {
+               Option o = (Option) map.get(name);
+               o.setValue(value);
+       }
+       
+       public String getPrefix(){
+               return prefix;
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/support/Fields.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/Fields.java        2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/Fields.java        2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,620 @@
+package freenet.support;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.StringTokenizer;
+
+/**
+ * This class contains static methods used for parsing boolean and unsigned
+ * long fields in Freenet messages. Also some general utility methods for
+ * dealing with string and numeric data.
+ * 
+ * @author oskar
+ */
+public abstract class Fields {
+
+       /**
+        * All possible chars for representing a number as a String. Used to
+        * optimize numberList().
+        */
+       private final static char[] digits =
+               {
+                       '0',
+                       '1',
+                       '2',
+                       '3',
+                       '4',
+                       '5',
+                       '6',
+                       '7',
+                       '8',
+                       '9',
+                       'a',
+                       'b',
+                       'c',
+                       'd',
+                       'e',
+                       'f',
+                       'g',
+                       'h',
+                       'i',
+                       'j',
+                       'k',
+                       'l',
+                       'm',
+                       'n',
+                       'o',
+                       'p',
+                       'q',
+                       'r',
+                       's',
+                       't',
+                       'u',
+                       'v',
+                       'w',
+                       'x',
+                       'y',
+                       'z' };
+
+       /**
+        * Converts a hex string into a long. Long.parseLong(hex, 16) assumes 
the
+        * input is nonnegative unless there is a preceding minus sign. This 
method
+        * reads the input as twos complement instead, so if the input is 8 
bytes
+        * long, it will correctly restore a negative long produced by
+        * Long.toHexString() but not neccesarily one produced by
+        * Long.toString(x,16) since that method will produce a string like 
'-FF'
+        * for negative longs values.
+        * 
+        * @param hex
+        *            A string in capital or lower case hex, of no more then 16
+        *            characters.
+        * @throws NumberFormatException
+        *             if the string is more than 16 characters long, or if any
+        *             character is not in the set [0-9a-fA-f]
+        */
+       public static final long hexToLong(String hex)
+               throws NumberFormatException {
+               int len = hex.length();
+               if (len > 16)
+                       throw new NumberFormatException();
+
+               long l = 0;
+               for (int i = 0; i < len; i++) {
+                       l <<= 4;
+                       int c = Character.digit(hex.charAt(i), 16);
+                       if (c < 0)
+                               throw new NumberFormatException();
+                       l |= c;
+               }
+               return l;
+       }
+
+       /**
+        * Converts a hex string into an int. Integer.parseInt(hex, 16) assumes 
the
+        * input is nonnegative unless there is a preceding minus sign. This 
method
+        * reads the input as twos complement instead, so if the input is 8 
bytes
+        * long, it will correctly restore a negative int produced by
+        * Integer.toHexString() but not neccesarily one produced by
+        * Integer.toString(x,16) since that method will produce a string like
+        * '-FF' for negative integer values.
+        * 
+        * @param hex
+        *            A string in capital or lower case hex, of no more then 16
+        *            characters.
+        * @throws NumberFormatException
+        *             if the string is more than 16 characters long, or if any
+        *             character is not in the set [0-9a-fA-f]
+        */
+       public static final int hexToInt(String hex) throws 
NumberFormatException {
+               int len = hex.length();
+               if (len > 16)
+                       throw new NumberFormatException();
+
+               int l = 0;
+               for (int i = 0; i < len; i++) {
+                       l <<= 4;
+                       int c = Character.digit(hex.charAt(i), 16);
+                       if (c < 0)
+                               throw new NumberFormatException();
+                       l |= c;
+               }
+               return l;
+       }
+
+       /**
+        * Finds the boolean value of the field, by doing a caseless match with 
the
+        * strings "true" and "false".
+        * 
+        * @param s
+        *            The string
+        * @param def
+        *            The default value if the string can't be parsed. If the
+        *            default is true, it checks that the string is not 
"false"; if
+        *            it is false, it checks whether the string is "true".
+        * @return the boolean field value or the default value if the field 
value
+        *         couldn't be parsed.
+        */
+       /* wooo, rocket science! (this is purely abstraction people) */
+       public static final boolean stringToBool(String s, boolean def) {
+               if(s == null) return def;
+               return (def ? !s.equalsIgnoreCase("false") : 
s.equalsIgnoreCase("true"));
+       }
+
+       /**
+        * Converts a boolean to a String of either "true" or "false".
+        * 
+        * @param b
+        *            the boolean value to convert.
+        * @return A "true" or "false" String.
+        */
+       public static final String boolToString(boolean b) {
+               return b ? "true" : "false";
+       }
+
+       public static final String[] commaList(String ls) {
+               StringTokenizer st = new StringTokenizer(ls, ",");
+               String[] r = new String[st.countTokens()];
+               for (int i = 0; i < r.length; i++) {
+                       r[i] = st.nextToken().trim();
+               }
+               return r;
+       }
+
+       public static final String commaList(String[] ls) {
+               return textList(ls, ',');
+       }
+
+       public static final String textList(String[] ls, char ch) {
+               StringBuffer sb = new StringBuffer();
+               for (int i = 0; i < ls.length; i++) {
+                       sb.append(ls[i]);
+                       if (i != ls.length - 1)
+                               sb.append(ch);
+               }
+               return sb.toString();
+       }
+
+       public static final long[] numberList(String ls)
+               throws NumberFormatException {
+               StringTokenizer st = new StringTokenizer(ls, ",");
+               long[] r = new long[st.countTokens()];
+               for (int i = 0; i < r.length; i++) {
+                       r[i] = hexToLong(st.nextToken());
+               }
+               return r;
+       }
+
+       public static final String numberList(long[] ls) {
+               char[] numberBuf = new char[64];
+               StringBuffer listBuf = new StringBuffer(ls.length * 18);
+               for (int i = 0; i < ls.length; i++) {
+
+                       // Convert the number into a string in a fixed size 
buffer.
+                       long l = ls[i];
+                       int charPos = 64;
+                       do {
+                               numberBuf[--charPos] = digits[(int) (l & 0x0F)];
+                               l >>>= 4;
+                       } while (l != 0);
+
+                       listBuf.append(numberBuf, charPos, (64 - charPos));
+                       if (i != ls.length - 1) {
+                               listBuf.append(',');
+                       }
+               }
+               return listBuf.toString();
+       }
+
+       /**
+        * Parses a time and date value, using a very strict format. The value 
has
+        * to be of the form YYYYMMDD-HH:MM:SS (where seconds may include a
+        * decimal) or YYYYMMDD (in which case 00:00:00 is assumed for time).
+        * Another accepted format is +/-{integer}{day|month|year|minute|second}
+        * 
+        * @return millis of the epoch of at the time described.
+        */
+       public static final long dateTime(String date)
+               throws NumberFormatException {
+
+               if (date.length() == 0)
+                       throw new NumberFormatException("Date time empty");
+
+               if ((date.charAt(0) == '-') || (date.charAt(0) == '+')) {
+                       // Relative date
+                       StringBuffer sb = new StringBuffer(10);
+                       for (int x = 1; x < date.length(); x++) {
+                               char c = date.charAt(x);
+                               if (Character.isDigit(c)) {
+                                       sb.append(c);
+                               } else
+                                       break;
+                       }
+                       int num = Integer.parseInt(sb.toString());
+                       int chop = 1 + sb.length();
+                       int deltaType = 0;
+                       if (date.length() == chop)
+                               deltaType = Calendar.DAY_OF_YEAR;
+                       else {
+                               String deltaTypeString = 
date.substring(chop).toLowerCase();
+                               if (deltaTypeString.equals("y")
+                                       || deltaTypeString.equals("year"))
+                                       deltaType = Calendar.YEAR;
+                               else if (
+                                       deltaTypeString.equals("month")
+                                               || deltaTypeString.equals("mo"))
+                                       deltaType = Calendar.MONTH;
+                               else if (
+                                       deltaTypeString.equals("week")
+                                               || deltaTypeString.equals("w"))
+                                       deltaType = Calendar.WEEK_OF_YEAR;
+                               else if (
+                                       deltaTypeString.equals("day")
+                                               || deltaTypeString.equals("d"))
+                                       deltaType = Calendar.DAY_OF_YEAR;
+                               else if (
+                                       deltaTypeString.equals("hour")
+                                               || deltaTypeString.equals("h"))
+                                       deltaType = Calendar.HOUR;
+                               else if (
+                                       deltaTypeString.equals("minute")
+                                               || 
deltaTypeString.equals("min"))
+                                       deltaType = Calendar.MINUTE;
+                               else if (
+                                       deltaTypeString.equals("second")
+                                               || deltaTypeString.equals("s")
+                                               || 
deltaTypeString.equals("sec"))
+                                       deltaType = Calendar.SECOND;
+                               else
+                                       throw new NumberFormatException(
+                                               "unknown time/date delta type: 
" + deltaTypeString);
+                               GregorianCalendar gc = new GregorianCalendar();
+                               gc.add(deltaType, (date.charAt(0) == '+') ? num 
: -num);
+                               return gc.getTime().getTime();
+                       }
+               }
+
+               int dash = date.indexOf('-');
+
+               if (!((dash == -1) && (date.length() == 8))
+                       && !((dash == 8) && (date.length() == 17)))
+                       throw new NumberFormatException(
+                               "Date time: " + date + " not correct.");
+               int year = Integer.parseInt(date.substring(0, 4));
+               int month = Integer.parseInt(date.substring(4, 6));
+               int day = Integer.parseInt(date.substring(6, 8));
+
+               int hour = dash == -1 ? 0 : Integer.parseInt(date.substring(9, 
11));
+               int minute = dash == -1 ? 0 : 
Integer.parseInt(date.substring(12, 14));
+               int second = dash == -1 ? 0 : 
Integer.parseInt(date.substring(15, 17));
+
+               // Note that month is zero based in GregorianCalender!
+               try {
+                       return (
+                               new GregorianCalendar(
+                                       year,
+                                       month - 1,
+                                       day,
+                                       hour,
+                                       minute,
+                                       second))
+                               .getTime()
+                               .getTime();
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       // The API docs don't say which exception is thrown on 
bad numbers!
+                       throw new NumberFormatException("Invalid date " + date 
+ ": " + e);
+               }
+
+       }
+
+       public static final String secToDateTime(long time) {
+               //Calendar c = Calendar.getInstance();
+               //c.setTime(new Date(time));
+               //gc.setTimeInMillis(time*1000);
+
+               DateFormat f = new SimpleDateFormat("yyyyMMdd-HH:mm:ss");
+               //String dateString = f.format(c.getTime());
+               String dateString = f.format(new Date(time * 1000));
+
+               if (dateString.endsWith("-00:00:00"))
+                       dateString = dateString.substring(0, 8);
+
+               return dateString;
+       }
+
+       public static final int compareBytes(byte[] b1, byte[] b2) {
+               int len = Math.max(b1.length, b2.length);
+               for (int i = 0; i < len; ++i) {
+                       if (i == b1.length)
+                               return i == b2.length ? 0 : -1;
+                       else if (i == b2.length)
+                               return 1;
+                       else if ((0xff & b1[i]) > (0xff & b2[i]))
+                               return 1;
+                       else if ((0xff & b1[i]) < (0xff & b2[i]))
+                               return -1;
+               }
+               return 0;
+       }
+
+       public static final int compareBytes(
+               byte[] a,
+               byte[] b,
+               int aoff,
+               int boff,
+               int len) {
+               for (int i = 0; i < len; ++i) {
+                       if (i + aoff == a.length)
+                               return i + boff == b.length ? 0 : -1;
+                       else if (i + boff == b.length)
+                               return 1;
+                       else if ((0xff & a[i + aoff]) > (0xff & b[i + boff]))
+                               return 1;
+                       else if ((0xff & a[i + aoff]) < (0xff & b[i + boff]))
+                               return -1;
+               }
+               return 0;
+       }
+
+       public static final boolean byteArrayEqual(byte[] a, byte[] b) {
+               if (a.length != b.length)
+                       return false;
+               for (int i = 0; i < a.length; ++i)
+                       if (a[i] != b[i])
+                               return false;
+               return true;
+       }
+
+       public static final boolean byteArrayEqual(
+               byte[] a,
+               byte[] b,
+               int aoff,
+               int boff,
+               int len) {
+               if ((a.length < aoff + len) || (b.length < boff + len))
+                       return false;
+               for (int i = 0; i < len; ++i)
+                       if (a[i + aoff] != b[i + boff])
+                               return false;
+               return true;
+       }
+
+       /**
+        * Compares byte arrays lexicographically.
+        */
+       public static final class ByteArrayComparator implements Comparator {
+               public final int compare(Object o1, Object o2) {
+                       return compare((byte[]) o1, (byte[]) o2);
+               }
+               public static final int compare(byte[] o1, byte[] o2) {
+                       return compareBytes(o1, o2);
+               }
+       }
+
+       // could add stuff like IntegerComparator, LongComparator etc.
+       // if we need it
+
+       public static final int hashCode(byte[] b) {
+           return hashCode(b, 0, b.length);
+       }
+       
+       /**
+        * A generic hashcode suited for byte arrays that are more or less 
random.
+        */
+       public static final int hashCode(byte[] b, int ptr, int length) {
+               int h = 0;
+               for (int i = length - 1; i >= 0; --i) {
+                       int x = b[ptr+i] & 0xff;
+                       h ^= x << ((i & 3) << 3);
+               }
+               return h;
+       }
+
+       /**
+        * Long version of above Not believed to be secure in any sense of the 
word :)
+        */
+       public static final long longHashCode(byte[] b) {
+               long h = 0;
+               for (int i = b.length - 1; i >= 0; --i) {
+                       int x = b[i] & 0xff;
+                       h ^= ((long) x) << ((i & 7) << 3);
+               }
+               return h;
+       }
+
+    /**
+     * @param addr
+     * @return
+     */
+    public static String commaList(Object[] addr) {
+               StringBuffer sb = new StringBuffer();
+               for (int i = 0; i < addr.length; i++) {
+                       sb.append(addr[i]);
+                       if (i != addr.length - 1)
+                               sb.append(',');
+               }
+               return sb.toString();
+    }
+
+    /**
+     * Convert an array of longs to an array of bytes, using a 
+     * consistent endianness.
+     */
+    public static byte[] longsToBytes(long[] longs) {
+        byte[] buf = new byte[longs.length * 8];
+        for(int i=0;i<longs.length;i++) {
+            long x = longs[i];
+            for(int j=0;j<8;j++) {
+                buf[i*8+j] = (byte)x;
+                x >>= 8;
+            }
+        }
+        return buf;
+    }
+    
+    /**
+     * Convert an array of bytes to an array of longs.
+     * @param buf
+     * @return
+     */
+    public static long[] bytesToLongs(byte[] buf) {
+        if(buf.length % 8 != 0) throw new IllegalArgumentException();
+        long[] longs = new long[buf.length/8];
+        for(int i=0;i<longs.length;i++) {
+            long x = 0;
+            for(int j=7;j>=0;j--) {
+                long y = (buf[i*8+j] & 0xff);
+                x = (x << 8) + y;
+            }
+            longs[i] = x;
+        }
+        return longs;
+    }
+
+    /**
+     * Convert an array of bytes to a single long.
+     */
+    public static long bytesToLong(byte[] buf) {
+        if(buf.length < 8) throw new IllegalArgumentException();
+        long x = 0;
+        for(int j=7;j>=0;j--) {
+            long y = (buf[j] & 0xff);
+            x = (x << 8) + y;
+        }
+        return x;
+    }
+
+    /**
+     * Convert an array of bytes to a single int.
+     */
+       public static int bytesToInt(byte[] buf, int offset) {
+        if(buf.length < 4) throw new IllegalArgumentException();
+        int x = 0;
+        for(int j=3;j>=0;j--) {
+            int y = (buf[j+offset] & 0xff);
+            x = (x << 8) + y;
+        }
+        return x;
+       }
+       
+    public static byte[] longToBytes(long x) {
+        byte[] buf = new byte[8];
+        for(int j=0;j<8;j++) {
+            buf[j] = (byte)x;
+            x >>= 8;
+        }
+        return buf;
+    }
+
+       public static byte[] intsToBytes(int[] ints) {
+        byte[] buf = new byte[ints.length * 8];
+        for(int i=0;i<ints.length;i++) {
+            long x = ints[i];
+            for(int j=0;j<4;j++) {
+                buf[i*4+j] = (byte)x;
+                x >>= 8;
+            }
+        }
+        return buf;
+       }
+
+    /**
+     * Parse a human-readable string possibly including SI units into a short.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static short parseShort(String s) throws NumberFormatException {
+               short res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10 };
+                       while ((x >= 0)
+                               && ((idx = "kK".indexOf(s.charAt(x))) != -1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Short.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+
+       /**
+        * Parse a human-readable string possibly including SI units into an 
integer.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static int parseInt(String s) throws NumberFormatException {
+               int res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10,
+                                       1000 * 1000,
+                                       1 << 20,
+                                       1000 * 1000 * 1000,
+                                       1 << 30 };
+                       while ((x >= 0)
+                               && ((idx = "kKmMgG".indexOf(s.charAt(x))) != 
-1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Integer.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+       
+       /**
+        * Parse a human-readable string possibly including SI units into a 
long.
+        * @throws NumberFormatException
+        *             if the string is not parseable
+        */
+       public static long parseLong(String s) throws NumberFormatException {
+               long res = 1;
+               int x = s.length() - 1;
+               int idx;
+               try {
+                       long[] l =
+                               {
+                                       1000,
+                                       1 << 10,
+                                       1000 * 1000,
+                                       1 << 20,
+                                       1000 * 1000 * 1000,
+                                       1 << 30,
+                                       1000 * 1000 * 1000 * 1000,
+                                       1 << 40,
+                                       1000 * 1000 * 1000 * 1000 * 1000,
+                                       1 << 50,
+                                       1000 * 1000 * 1000 * 1000 * 1000 * 1000,
+                                       1 << 60 };
+                       while ((x >= 0)
+                               && ((idx = "kKmMgGtTpPeE".indexOf(s.charAt(x))) 
!= -1)) {
+                               x--;
+                               res *= l[idx];
+                       }
+                       res *= Double.parseDouble(s.substring(0, x + 1));
+               } catch (ArithmeticException e) {
+                       res = Long.MAX_VALUE;
+                       throw new NumberFormatException(e.getMessage());
+               }
+               return res;
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/support/LineReader.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/LineReader.java    2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/LineReader.java    2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,12 @@
+package freenet.support;
+
+import java.io.IOException;
+
+public interface LineReader {
+
+       /**
+        * Read a \n or \r\n terminated line of UTF-8 or ISO-8859-1.
+        */
+       public String readLine(int maxLength, int bufferSize, boolean utf) 
throws IOException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/support/SimpleFieldSet.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/SimpleFieldSet.java        
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/SimpleFieldSet.java        
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,479 @@
+package freenet.support;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import freenet.support.io.LineReader;
+
+/**
+ * @author amphibian
+ * 
+ * Very very simple FieldSet type thing, which uses the standard
+ * Java facilities.
+ */
+public class SimpleFieldSet {
+
+    private final Map values;
+    private Map subsets;
+    private String endMarker;
+    static public final char MULTI_LEVEL_CHAR = '.';
+    
+    public SimpleFieldSet(BufferedReader br) throws IOException {
+        values = new HashMap();
+               subsets = null;
+        read(br);
+    }
+
+    public SimpleFieldSet(LineReader lis, int maxLineLength, int 
lineBufferSize, boolean tolerant, boolean utf8OrIso88591) throws IOException {
+       values = new HashMap();
+               subsets = null;
+       read(lis, maxLineLength, lineBufferSize, tolerant, utf8OrIso88591);
+    }
+    
+    /**
+     * Empty constructor
+     */
+    public SimpleFieldSet() {
+        values = new HashMap();
+               subsets = null;
+    }
+
+    /**
+     * Construct from a string.
+     * @throws IOException if the string is too short or invalid.
+     */
+    public SimpleFieldSet(String content, boolean multiLevel) throws 
IOException {
+       values = new HashMap();
+       subsets = null;
+        StringReader sr = new StringReader(content);
+        BufferedReader br = new BufferedReader(sr);
+           read(br);
+    }
+    
+    /**
+     * Read from disk
+     * Format:
+     * blah=blah
+     * blah=blah
+     * End
+     */
+    private void read(BufferedReader br) throws IOException {
+        boolean firstLine = true;
+        while(true) {
+            String line = br.readLine();
+            if(line == null) {
+                if(firstLine) throw new EOFException();
+                throw new IOException();
+            }
+            firstLine = false;
+            int index = line.indexOf('=');
+            if(index >= 0) {
+                // Mapping
+                String before = line.substring(0, index);
+                String after = line.substring(index+1);
+                put(before, after);
+            } else {
+               endMarker = line;
+               return;
+            }
+            
+        }
+    }
+
+    /**
+     * Read from disk
+     * Format:
+     * blah=blah
+     * blah=blah
+     * End
+     * @param utfOrIso88591 If true, read as UTF-8, otherwise read as 
ISO-8859-1.
+     */
+    private void read(LineReader br, int maxLength, int bufferSize, boolean 
tolerant, boolean utfOrIso88591) throws IOException {
+        boolean firstLine = true;
+        while(true) {
+            String line = br.readLine(maxLength, bufferSize, utfOrIso88591);
+            if(line == null) {
+                if(firstLine) throw new EOFException();
+                if(!tolerant)
+                       throw new IOException("No end marker");
+                return;
+            }
+            if((line.length() == 0) && tolerant) continue; // ignore
+            firstLine = false;
+            int index = line.indexOf('=');
+            if(index >= 0) {
+                // Mapping
+                String before = line.substring(0, index);
+                String after = line.substring(index+1);
+                put(before, after);
+            } else {
+               endMarker = line;
+               return;
+            }
+            
+        }
+    }
+    
+    public synchronized String get(String key) {
+               int idx = key.indexOf(MULTI_LEVEL_CHAR);
+               if(idx == -1)
+                       return (String) values.get(key);
+               else if(idx == 0)
+                       return null;
+               else {
+                       if(subsets == null) return null;
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = (SimpleFieldSet) 
(subsets.get(before));
+                       if(fs == null) return null;
+                       return fs.get(after);
+               }
+    }
+    
+    public String[] getAll(String key) {
+       return split(get(key));
+    }
+
+    private static final String[] split(String string) {
+       if(string == null) return new String[0];
+       return string.split(";"); // slower???
+//     int index = string.indexOf(';');
+//     if(index == -1) return null;
+//     Vector v=new Vector();
+//     v.removeAllElements();
+//        while(index>0){
+//            // Mapping
+//            String before = string.substring(0, index);         
+//            String after = string.substring(index+1);
+//            v.addElement(before);
+//            string=after;
+//            index = string.indexOf(';');
+//        }
+//     
+//     return (String[]) v.toArray();
+       }
+
+       public synchronized void put(String key, String value) {
+               int idx;
+               if(value == null) return;
+               if((idx = key.indexOf(MULTI_LEVEL_CHAR)) == -1) {
+                       String x = (String) values.get(key);
+                       
+                       if(x == null) {
+                               values.put(key, value);
+                       } else {
+                               values.put(key, 
((String)values.get(key))+";"+value);
+                       }
+               } else {
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = null;
+                       if(subsets == null)
+                               subsets = new HashMap();
+                       fs = (SimpleFieldSet) (subsets.get(before));
+                       if(fs == null) {
+                               fs = new SimpleFieldSet();
+                               subsets.put(before, fs);
+                       }
+                       fs.put(after, value);
+               }
+    }
+
+       public void put(String key, int value) {
+               put(key, Integer.toString(value));
+       }
+       
+       public void put(String key, long value) {
+               put(key, Long.toString(value));
+       }
+       
+       public void put(String key, short value) {
+               put(key, Short.toString(value));
+       }
+       
+       public void put(String key, char c) {
+               put(key, ""+c);
+       }
+       
+       public void put(String key, boolean b) {
+               put(key, Boolean.toString(b));
+       }
+       
+       public void put(String key, double windowSize) {
+               put(key, Double.toString(windowSize));
+       }
+
+    /**
+     * Write the contents of the SimpleFieldSet to a Writer.
+     * @param osr
+     */
+       public void writeTo(Writer w) throws IOException {
+               writeTo(w, "", false);
+       }
+       
+    synchronized void writeTo(Writer w, String prefix, boolean noEndMarker) 
throws IOException {
+       for(Iterator i = values.entrySet().iterator();i.hasNext();) {
+            Map.Entry entry = (Map.Entry) i.next();
+            String key = (String) entry.getKey();
+            String value = (String) entry.getValue();
+            w.write(prefix+key+"="+value+"\n");
+       }
+       if(subsets != null) {
+               for(Iterator i = subsets.entrySet().iterator();i.hasNext();) {
+                       Map.Entry entry = (Map.Entry) i.next();
+                       String key = (String) entry.getKey();
+                       SimpleFieldSet subset = (SimpleFieldSet) 
entry.getValue();
+                       if(subset == null) throw new NullPointerException();
+                       subset.writeTo(w, prefix+key+MULTI_LEVEL_CHAR, true);
+               }
+       }
+       if(!noEndMarker) {
+               if(endMarker == null)
+                       w.write("End\n");
+               else
+                       w.write(endMarker+"\n");
+       }
+    }
+    
+    public String toString() {
+        StringWriter sw = new StringWriter();
+        try {
+            writeTo(sw);
+        } catch (IOException e) {
+            System.err.println("WTF?!: "+e+" in toString()!"+e);
+        }
+        return sw.toString();
+    }
+    
+    public String getEndMarker() {
+       return endMarker;
+    }
+    
+    public void setEndMarker(String s) {
+       endMarker = s;
+    }
+
+       public synchronized SimpleFieldSet subset(String key) {
+               if(subsets == null) return null;
+               int idx = key.indexOf(MULTI_LEVEL_CHAR);
+               if(idx == -1)
+                       return (SimpleFieldSet) subsets.get(key);
+               String before = key.substring(0, idx);
+               String after = key.substring(idx+1);
+               SimpleFieldSet fs = (SimpleFieldSet) subsets.get(before);
+               if(fs == null) return null;
+               return fs.subset(after);
+       }
+
+       public Iterator keyIterator() {
+               return new KeyIterator("");
+       }
+
+       KeyIterator keyIterator(String prefix) {
+               return new KeyIterator(prefix);
+       }
+       
+    public class KeyIterator implements Iterator {
+       
+       final Iterator valuesIterator;
+       final Iterator subsetIterator;
+       KeyIterator subIterator;
+       String prefix;
+       
+       public KeyIterator(String prefix) {
+               valuesIterator = values.keySet().iterator();
+               if(subsets != null)
+                       subsetIterator = subsets.keySet().iterator();
+               else
+                       subsetIterator = null;
+               this.prefix = prefix;
+       }
+
+               public boolean hasNext() {
+                       synchronized(SimpleFieldSet.this) {
+                               if(valuesIterator.hasNext()) return true;
+                               if((subIterator != null) && 
subIterator.hasNext()) return true;
+                               if(subIterator != null) subIterator = null;
+                               return false;
+                       }
+               }
+
+               public final Object next() {
+                       return nextKey();
+               }
+               
+               public String nextKey() {
+                       synchronized(SimpleFieldSet.this) {
+                               String ret = null;
+                               if(ret == null && valuesIterator.hasNext()) {
+                                       return prefix + valuesIterator.next();
+                               }
+                               while(true) {
+                                       // Iterate subsets.
+                                       if(subIterator != null && 
subIterator.hasNext()) {
+                                               if(ret != null)
+                                                       // Found next but one, 
can return next
+                                                       return ret;
+                                               ret = (String) 
subIterator.next();
+                                               if(subIterator.hasNext()) {
+                                                       if(ret != null) return 
ret;
+                                               } else {
+                                                       subIterator = null;
+                                               }
+                                       } else
+                                               subIterator = null;
+                                       if(subsetIterator != null && 
subsetIterator.hasNext()) {
+                                               String key = (String) 
subsetIterator.next();
+                                               SimpleFieldSet fs = 
(SimpleFieldSet) subsets.get(key);
+                                               String newPrefix = prefix + key 
+ MULTI_LEVEL_CHAR;
+                                               subIterator = 
fs.keyIterator(newPrefix);
+                                       }
+                               }
+                       }
+               }
+
+               public synchronized void remove() {
+                       throw new UnsupportedOperationException();
+               }
+       }
+
+       public void put(String key, SimpleFieldSet fs) {
+               if(fs == null) return; // legal no-op, because used everywhere
+               if(fs.isEmpty())
+                       throw new IllegalArgumentException("Empty");
+               if(subsets == null)
+                       subsets = new HashMap();
+               if(subsets.containsKey(key))
+                       throw new IllegalArgumentException("Already contains 
"+key+" but trying to add a SimpleFieldSet!");
+               subsets.put(key, fs);
+       }
+
+       public synchronized void removeValue(String key) {
+               int idx;
+               if((idx = key.indexOf(MULTI_LEVEL_CHAR)) == -1) {
+                       values.remove(key);
+               } else {
+                       if(subsets == null) return;
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = (SimpleFieldSet) 
(subsets.get(before));
+                       if(fs == null) {
+                               return;
+                       }
+                       fs.removeValue(after);
+                       if(fs.isEmpty()) {
+                               subsets.remove(before);
+                               if(subsets.isEmpty())
+                                       subsets = null;
+                       }
+               }
+       }
+
+       public synchronized void removeSubset(String key) {
+               if(subsets == null) return;
+               int idx;
+               if((idx = key.indexOf(MULTI_LEVEL_CHAR)) == -1) {
+                       subsets.remove(key);
+               } else {
+                       String before = key.substring(0, idx);
+                       String after = key.substring(idx+1);
+                       SimpleFieldSet fs = (SimpleFieldSet) 
(subsets.get(before));
+                       if(fs == null) {
+                               return;
+                       }
+                       fs.removeSubset(after);
+                       if(fs.isEmpty()) {
+                               subsets.remove(before);
+                               if(subsets.isEmpty())
+                                       subsets = null;
+                       }
+               }
+       }
+       
+       /** Is this SimpleFieldSet empty? */
+       public boolean isEmpty() {
+               return values.isEmpty() && (subsets == null || 
subsets.isEmpty());
+       }
+
+       public Iterator directSubsetNameIterator() {
+               return subsets.keySet().iterator();
+       }
+
+       public String[] namesOfDirectSubsets() {
+               return (String[]) subsets.keySet().toArray(new 
String[subsets.size()]);
+       }
+
+       public static SimpleFieldSet readFrom(File f) throws IOException {
+               FileInputStream fis = null;
+               try {
+                       fis = new FileInputStream(f);
+                       BufferedInputStream bis = new BufferedInputStream(fis);
+                       InputStreamReader isr;
+                       try {
+                               isr = new InputStreamReader(bis, "UTF-8");
+                       } catch (UnsupportedEncodingException e) {
+                               System.err.println("Impossible: "+e);
+                               fis.close();
+                               return null;
+                       }
+                       BufferedReader br = new BufferedReader(isr);
+                       SimpleFieldSet fs = new SimpleFieldSet(br);
+                       br.close();
+                       fis = null;
+                       return fs;
+               } finally {
+                       try {
+                               if(fis != null) fis.close();
+                       } catch (IOException e) {
+                               // Ignore
+                       }
+               }
+       }
+
+       public long getInt(String key, int def) {
+               String s = get(key);
+               if(s == null) return def;
+               try {
+                       return Integer.parseInt(s);
+               } catch (NumberFormatException e) {
+                       return def;
+               }
+       }
+
+       public double getDouble(String key, double def) {
+               String s = get(key);
+               if(s == null) return def;
+               try {
+                       return Double.parseDouble(s);
+               } catch (NumberFormatException e) {
+                       return def;
+               }
+       }
+
+       public long getLong(String key, long def) {
+               String s = get(key);
+               if(s == null) return def;
+               try {
+                       return Long.parseLong(s);
+               } catch (NumberFormatException e) {
+                       return def;
+               }
+       }
+
+       public boolean getBoolean(String key, boolean def) {
+               return Fields.stringToBool(get(key), def);
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/support/URLDecoder.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/URLDecoder.java    2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/URLDecoder.java    2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,104 @@
+package freenet.support;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/*
+  This code is part of the Java Adaptive Network Client by Ian Clarke. 
+  It is distributed under the GNU Public Licence (GPL) version 2.  See
+  http://www.gnu.org/ for further details of the GPL.
+*/
+
+
+/**
+ * The class contains a utility method for converting a 
+ * <code>String</code> out of a MIME format called 
+ * "<code>x-www-form-urlencoded</code>" format. 
+ * <p>
+ * To convert a <code>String</code>, each character is examined in turn:
+ * <ul>
+ * <li>The ASCII characters '<code>a</code>' through '<code>z</code>', 
+ *     '<code>A</code>' through '<code>Z</code>', and '<code>0</code>' 
+ *     through '<code>9</code>' remain the same. 
+ * <li>The plus sign '<code>+</code>' is converted into a 
+ *     space character '<code>&nbsp;</code>'. 
+ * <li>The percent sign '<code>%</code>' must be followed by a 
+ *     two-digit hexadecimal number, and is converted into the
+ *     corresponding 8-bit character.
+ * <li>The following "safe" characters [RFC 1738] are passed as is,
+ *     if they appear:
+ *     <code>$ - _ . + ! * ' ( ) ,</code>
+ * <li>Anything else encountered, though strictly speaking illegal, 
+ *     is passed as is.
+ * </ul>
+ *
+ * @author <a href="http://www.doc.ic.ac.uk/~twh1/";>Theodore Hong</a>
+ **/
+
+public class URLDecoder
+{
+    // test harness
+    public static void main(String[] args) throws URLEncodedFormatException {
+       for (int i = 0; i < args.length; i++) {
+           System.out.println(args[i] + " -> " + decode(args[i]));
+       }
+    }
+
+    /**
+     * Characters which will be passed unaltered.
+     **/
+    private static final String safeCharList = "$-_.+!*'(),";
+
+    /**
+        * Translates a string out of x-www-form-urlencoded format.
+        *
+        * @param s String to be translated.
+        * @return the translated String.
+        *
+        **/
+       public static String decode(String s) throws URLEncodedFormatException {
+               if (s.length() == 0)
+                       return "";
+               int len = s.length();
+               ByteArrayOutputStream decodedBytes = new 
ByteArrayOutputStream();
+
+               for (int i = 0; i < len; i++) {
+                       char c = s.charAt(i);
+                       if (Character.isLetterOrDigit(c))
+                               decodedBytes.write(c);
+                       else if (c == '+')
+                               decodedBytes.write(' ');
+                       else if (safeCharList.indexOf(c) != -1)
+                               decodedBytes.write(c);
+                       else if (c == '%') {
+                               if (i >= len - 2) {
+                                       throw new URLEncodedFormatException(s);
+                               }
+                               char[] hexChars = new char[2];
+
+                               hexChars[0] = s.charAt(++i);
+                               hexChars[1] = s.charAt(++i);
+
+                               String hexval = new String(hexChars);
+                               try {
+                                       long read = Fields.hexToLong(hexval);
+                                       if (read == 0)
+                                               throw new 
URLEncodedFormatException("Can't encode" + " 00");
+                                       decodedBytes.write((int) read);
+                               } catch (NumberFormatException nfe) {
+                                       throw new URLEncodedFormatException(s);
+                               }
+                       } else
+                               decodedBytes.write(c);
+                       // throw new URLEncodedFormatException(s);
+               }
+               try {
+                       decodedBytes.close();
+                       return new String(decodedBytes.toByteArray(), "utf-8");
+               } catch (IOException ioe1) {
+                       /* if this throws something's wrong */
+               }
+               throw new URLEncodedFormatException(s);
+       }
+
+}

Added: trunk/apps/blueBunny/src/freenet/support/URLEncodedFormatException.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/URLEncodedFormatException.java     
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/URLEncodedFormatException.java     
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,20 @@
+package freenet.support;
+
+/*
+  This code is part of the Java Adaptive Network Client by Ian Clarke. 
+  It is distributed under the GNU Public Licence (GPL) version 2.  See
+  http://www.gnu.org/ for further details of the GPL.
+*/
+
+
+/**
+ * Thrown when trying to decode a string which is not in 
+ * "<code>x-www-form-urlencoded</code>" format.
+ **/
+
+public class URLEncodedFormatException extends Exception {
+       private static final long serialVersionUID = -1;
+       
+    URLEncodedFormatException () {}
+    URLEncodedFormatException (String s) { super(s); }
+}

Added: trunk/apps/blueBunny/src/freenet/support/URLEncoder.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/URLEncoder.java    2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/URLEncoder.java    2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,39 @@
+package freenet.support;
+
+public class URLEncoder {
+  // Moved here from FProxy by amphibian
+  final static String safeURLCharacters = 
"@*-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
+
+  /**
+   * Encode a string for inclusion in HTML tags
+   *
+   * @param  URL  String to encode
+   * @return      HTML-safe version of string
+   */
+  public final static String encode(String URL) {
+    StringBuffer enc = new StringBuffer(URL.length());
+    for (int i = 0; i < URL.length(); ++i) {
+      char c = URL.charAt(i);
+      if (safeURLCharacters.indexOf(c) >= 0) {
+        enc.append(c);
+      } else {
+        // Too harsh.
+        // if (c < 0 || c > 255)
+        //    throw new RuntimeException("illegal code "+c+" of char 
'"+URL.charAt(i)+"'");
+        // else
+
+        // Just keep lsb like:
+        // http://java.sun.com/j2se/1.3/docs/api/java/net/URLEncoder.html
+        c = (char) (c & '\u00ff');
+        if (c < 16) {
+          enc.append("%0");
+        } else {
+          enc.append("%");
+        }
+        enc.append(Integer.toHexString(c));
+      }
+    }
+    return enc.toString();
+  }
+
+}

Added: trunk/apps/blueBunny/src/freenet/support/io/LineReader.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/io/LineReader.java 2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/io/LineReader.java 2006-08-13 
22:14:40 UTC (rev 10071)
@@ -0,0 +1,12 @@
+package freenet.support.io;
+
+import java.io.IOException;
+
+public interface LineReader {
+
+       /**
+        * Read a \n or \r\n terminated line of UTF-8 or ISO-8859-1.
+        */
+       public String readLine(int maxLength, int bufferSize, boolean utf) 
throws IOException;
+       
+}

Added: trunk/apps/blueBunny/src/freenet/support/io/LineReadingInputStream.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/io/LineReadingInputStream.java     
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/io/LineReadingInputStream.java     
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,49 @@
+package freenet.support.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A FilterInputStream which provides readLine().
+ */
+public class LineReadingInputStream extends FilterInputStream implements 
LineReader {
+
+       public LineReadingInputStream(InputStream in) {
+               super(in);
+       }
+
+       private byte[] buf;
+
+       /**
+        * Read a \n or \r\n terminated line of UTF-8 or ISO-8859-1.
+        */
+       public String readLine(int maxLength, int bufferSize, boolean utf) 
throws IOException {
+               if(maxLength < bufferSize)
+                       bufferSize = maxLength;
+               if(buf == null)
+                       buf = new byte[Math.max(Math.min(128,maxLength), 
Math.min(1024, bufferSize))];
+               int ctr = 0;
+               while(true) {
+                       int x = read();
+                       if(x == -1) {
+                               if(ctr == 0) return null;
+                               return new String(buf, 0, ctr, utf ? "UTF-8" : 
"ISO-8859-1");
+                       }
+                       // REDFLAG this is definitely safe with the above 
charsets, it may not be safe with some wierd ones. 
+                       if(x == '\n') {
+                               if(ctr == 0) return "";
+                               if(buf[ctr-1] == '\r') ctr--;
+                               return new String(buf, 0, ctr, utf ? "UTF-8" : 
"ISO-8859-1");
+                       }
+                       if(ctr >= buf.length) {
+                               if(buf.length == maxLength) throw new 
TooLongException();
+                               byte[] newBuf = new byte[Math.min(buf.length * 
2, maxLength)];
+                               System.arraycopy(buf, 0, newBuf, 0, buf.length);
+                               buf = newBuf;
+                       }
+                       buf[ctr++] = (byte)x;
+               }
+       }
+       
+}

Added: trunk/apps/blueBunny/src/freenet/support/io/TooLongException.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/support/io/TooLongException.java   
2006-08-13 22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/support/io/TooLongException.java   
2006-08-13 22:14:40 UTC (rev 10071)
@@ -0,0 +1,9 @@
+package freenet.support.io;
+
+import java.io.IOException;
+
+/** Exception thrown by a LineReadingInputStream when a line is too long. */
+public class TooLongException extends IOException {
+       private static final long serialVersionUID = -1;
+
+}
\ No newline at end of file

Modified: trunk/apps/blueBunny/src/freenet/systray/Systray.java
===================================================================
--- trunk/apps/blueBunny/src/freenet/systray/Systray.java       2006-08-13 
22:01:32 UTC (rev 10070)
+++ trunk/apps/blueBunny/src/freenet/systray/Systray.java       2006-08-13 
22:14:40 UTC (rev 10071)
@@ -11,11 +11,30 @@
 import java.awt.event.ActionListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
+import java.io.File;
+import java.io.IOException;

+import freenet.config.FilePersistentConfig;
+
 public class Systray {
        private boolean isNodeAlive;
+       private static FilePersistentConfig cfg;

        public static void main(String[] args) {
+               // Load the config
+               File configFilename = new File("systray.ini");
+               try{
+               cfg = new FilePersistentConfig(configFilename); 
+       }catch(IOException e){
+               System.out.println("Error : "+e);
+               e.printStackTrace();
+               System.exit(-1);
+       }
+       cfg.finishedInit();
+       cfg.store();
+
+       //SubConfig loggingConfig = new SubConfig("node", cfg);
+       
                Systray s = new Systray();
        }

@@ -25,13 +44,13 @@
                if (SystemTray.isSupported()) {
                        final TrayIcon trayIcon;
                        SystemTray tray = SystemTray.getSystemTray();
-                       Image image = 
Toolkit.getDefaultToolkit().getImage("logo.jpg");
+                       Image image = 
Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/freenet/systray/resources/logo.jpg"));

                        MouseListener mouseListener = new MouseListener() {

                                public void mouseClicked(MouseEvent e) {
                                        System.out.println("Tray Icon - Mouse 
clicked!");
-                                       if(isNodeAlive && (e.getButton() == 
MouseEvent.BUTTON1))
+                                       if(isNodeAlive && (e.getButton() == 1))
                                                
BareBonesBrowserLaunch.launch("http://127.0.0.1:8888/";);
                                }

@@ -88,11 +107,14 @@
                        openConfigItem.addActionListener(openConfigListener);
                        openConfigItem.setEnabled(false);
                        popup.add(exitItem);
+                       popup.addSeparator();
                        popup.add(openFproxyItem);
                        popup.add(openWebsiteItem);
+                       popup.addSeparator();
                        popup.add(openConfigItem);
+                       popup.setLabel("Freenet 0.7");

-                       trayIcon = new TrayIcon(image, "Tray Demo", popup);
+                       trayIcon = new TrayIcon(image, "Freenet 0.7", popup);

                        ActionListener actionListener = new ActionListener() {
                                public void actionPerformed(ActionEvent e) {


Reply via email to