Index: applications/editors/josm/plugins/pluginmanager/build.xml
===================================================================
--- applications/editors/josm/plugins/pluginmanager/build.xml	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/build.xml	(revision 3335)
@@ -0,0 +1,58 @@
+<project name="pluginmanager" default="dist" basedir=".">
+
+	<property name="josm.build.dir" value="../JOSM"/>
+	<property name="josm.home.dir" value="${user.home}/.josm"/>
+	<property name="plugin.build.dir" value="bin"/>
+
+
+	
+	<target name="dist" depends="compile">
+		<!-- images -->
+		<!-- 
+		<copy todir="${plugin.build.dir}/images">
+			<fileset dir="src/images" />
+		</copy>
+		-->
+			<!-- copy configuration xml files -->
+		<copy todir="${plugin.build.dir}">
+			<fileset dir="src"> 
+				<include name="*.xml"/>
+  		</fileset>
+		</copy>
+		
+		<!-- create josm-custom.jar -->
+		<jar destfile="${ant.project.name}.jar" basedir="${plugin.build.dir}">
+			<manifest>
+        <attribute name="Plugin-Class" value="at.dallermassl.josm.plugin.pluginmanager.PluginManagerPlugin" />
+        <attribute name="Plugin-Description" value="Manage plugins and provide update mechanism." />
+        <attribute name="Plugin-Version" value="0.1" />
+        <!--attribute name="Plugin-Dependencies" value="org.eigenheimstrasse.josm" /-->
+			</manifest>
+		</jar>
+	</target>
+
+	<target name="compile" depends="init">
+		<mkdir dir="${plugin.build.dir}"/>
+		<javac srcdir="src" destdir="${plugin.build.dir}" debug="true" source="1.5" target="1.5">
+			<classpath>
+				<pathelement path="${josm.build.dir}/build"/>
+	      <fileset dir="${josm.build.dir}/lib">
+	        <include name="**/*.jar"/>
+	      </fileset>
+			</classpath>
+	  </javac>
+	</target>
+
+	<target name="install" depends="dist">
+		<copy file="${ant.project.name}.jar" todir="${josm.home.dir}/plugins" />
+	</target>
+
+  <target name="init">
+  	 <echo>java version: ${java.version}</echo>
+	 </target>
+
+	<target name="clean">
+		<delete dir="${plugin.build.dir}" />
+	</target>
+
+</project>
Index: applications/editors/josm/plugins/pluginmanager/site/josm-site.xml
===================================================================
--- applications/editors/josm/plugins/pluginmanager/site/josm-site.xml	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/site/josm-site.xml	(revision 3335)
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<site version="1.0">
+  <!-- meta data of site -->
+  <site-info>
+	  <site-name>Update Site of Christof Dallermassl</site-name>
+	  <site-url>http://www.tegmento.org/~cdaller/josm/</site-url>
+  </site-info>
+
+  <!-- plugins available on this site -->
+  <plugins>
+    <plugin id="surveyor" version="1.0">
+      <name>Surveyor</name>
+      <description lang="de">Erlaubt das Hinzufügen von Markern an der aktuellen GPS Position (vom LiveGps Plugin).</description>
+      <description lang="en">Allows to add markers at the current gps postion (from livegps plugins).</description>
+      <resources>
+        <resource src="http://www.tegmento.org/~cdaller/josm/surveyor.jar"
+                  target="${josm.user.dir}/plugins/surveyor.jar"/>
+        <!--
+        <resource src="http://www.tegmento.org/~cdaller/surveyor.xml"
+                  target="${josm.user.dir}/test/plugins/surveyor/surveyor.xml"/>
+        -->
+      </resources>
+    </plugin>
+
+    <plugin id="livegps" version="1.0">
+      <name>LiveGPS</name>
+      <description lang="en">Reads position from a gps device and shows it on the map.</description>
+      <resources>
+        <resource src="http://www.tegmento.org/~cdaller/josm/livegps.jar"
+                  target="${josm.user.dir}/plugins/livegps.jar"/>
+      </resources>
+    </plugin>
+
+    <plugin id="colorscheme" version="0.5">
+      <name>Color Scheme</name>
+      <description lang="en">Allows the user to create and use multiple colorschemes.</description>
+      <resources>
+        <resource src="http://www.tegmento.org/~cdaller/josm/colorscheme.jar"
+                  target="${josm.user.dir}/plugins/colorscheme.jar"/>
+      </resources>
+    </plugin>
+
+  </plugins>
+
+  <!-- include other update site locations -->
+  <sites>
+    <site ref="http://www.other.update.site.org/update"/>
+  </sites>
+</site> 
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/InstallPanel.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/InstallPanel.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/InstallPanel.java	(revision 3335)
@@ -0,0 +1,106 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableColumn;
+
+/**
+ * @author cdaller
+ *
+ */
+public class InstallPanel extends JPanel implements ListSelectionListener {
+    private JTable table;
+    private PluginTableModel pluginModel;
+    private JScrollPane scrollpane;
+    private JTextArea infoBox;
+
+    /**
+     * @param pluginUpdateFrame
+     * @param descriptions 
+     */
+    public InstallPanel(PluginUpdateFrame pluginUpdateFrame, List<SiteDescription> descriptions) {
+        super(new BorderLayout(12,12));
+        setBorder(new javax.swing.border.EmptyBorder(12,12,12,12));
+        final JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true);
+        
+        /* Setup the table */
+        table = new JTable(pluginModel = new PluginTableModel(descriptions));
+        table.setShowGrid(false);
+        table.setIntercellSpacing(new Dimension(0,0));
+        table.setRowHeight(table.getRowHeight() + 2);
+        table.setPreferredScrollableViewportSize(new Dimension(500,200));
+        table.getSelectionModel().addListSelectionListener(this);
+        
+        TableColumn col1 = table.getColumnModel().getColumn(0);
+        TableColumn col2 = table.getColumnModel().getColumn(1);
+        TableColumn col3 = table.getColumnModel().getColumn(2);
+//        TableColumn col4 = table.getColumnModel().getColumn(3);
+//        TableColumn col5 = table.getColumnModel().getColumn(4);
+
+        col1.setPreferredWidth(30);
+        col1.setMinWidth(30);
+        col1.setMaxWidth(30);
+        col1.setResizable(false);
+
+        col2.setPreferredWidth(180);
+        col3.setPreferredWidth(130);
+//        col4.setPreferredWidth(70);
+//        col5.setPreferredWidth(70);
+
+
+        scrollpane = new JScrollPane(table);
+        scrollpane.getViewport().setBackground(table.getBackground());
+        split.setTopComponent(scrollpane);
+
+        /* Create description */
+        JScrollPane infoPane = new JScrollPane(infoBox = new JTextArea());
+        infoPane.setPreferredSize(new Dimension(500,100));
+        split.setBottomComponent(infoPane);
+
+        SwingUtilities.invokeLater(new Runnable()
+        {
+            public void run()
+            {
+                split.setDividerLocation(0.75);
+            }
+        });
+        add(BorderLayout.CENTER,split);
+    }
+
+    /**
+     * 
+     */
+    public void install() {
+        System.out.println("Installing selected plugins");
+        pluginModel.install();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
+     */
+    public void valueChanged(ListSelectionEvent e) {
+        String text = "";
+        if (table.getSelectedRowCount() == 1)
+        {
+            PluginDescription plugin = pluginModel.getPlugins().get(table.getSelectedRow());
+            text = plugin.getDescription();
+        }
+        infoBox.setText(text);
+        infoBox.setCaretPosition(0);
+        
+    }
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginDescription.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginDescription.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginDescription.java	(revision 3335)
@@ -0,0 +1,144 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginDescription {
+    private boolean install;
+    private String id;
+    private String version;
+    private String installedVersion;
+    private String name;
+    private String description;
+    private List<PluginResource> resources = new ArrayList<PluginResource>();
+    
+    /**
+     * Add a resource to the plugin description.
+     * @param resource the resource to add.
+     */
+    public void addPluginResource(PluginResource resource) {
+        resources.add(resource);
+    }
+    
+    /**
+     * @return the id
+     */
+    public String getId() {
+        return this.id;
+    }
+    /**
+     * @param id the id to set
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+    /**
+     * @return the version
+     */
+    public String getVersion() {
+        return this.version;
+    }
+    /**
+     * @param version the version to set
+     */
+    public void setVersion(String version) {
+        this.version = version;
+    }
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return this.name;
+    }
+    /**
+     * @param name the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+    /**
+     * @return the description
+     */
+    public String getDescription() {
+        return this.description;
+    }
+    /**
+     * @param description the description to set
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * @return the resources
+     */
+    public List<PluginResource> getResources() {
+        return this.resources;
+    }
+    
+    /**
+     * @return the installedVersion
+     */
+    public String getInstalledVersion() {
+        return this.installedVersion;
+    }
+
+    /**
+     * @param installedVersion the installedVersion to set
+     */
+    public void setInstalledVersion(String installedVersion) {
+        this.installedVersion = installedVersion;
+    }
+
+    /**
+     * Returns <code>true</code> if the plugin was selected to install.
+     * @return <code>true</code> if the plugin was selected to install.
+     */
+    public boolean isInstall() {
+        return this.install;
+    }
+
+    /**
+     * @param install the install to set
+     */
+    public void setInstall(boolean install) {
+        this.install = install;
+    }
+
+    /**
+     * Copies all resources from the update site into the target directory.
+     */
+    public void install() {
+        for(PluginResource resource : resources) {
+            resource.install();
+            if(resource.getErrorMessage() != null) {
+                System.err.println("ERROR: " + resource.getErrorMessage());
+            }
+            if(resource.getErrorException() != null) {
+                resource.getErrorException().printStackTrace();
+            }
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return getClass().getSimpleName() + "[id=" + id 
+                                          + ", name=" + name 
+                                          + ", version=" + version 
+                                          + ", desc=" + description 
+                                          + ", resources=" + resources 
+                                          + "]";
+    }
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginHelper.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginHelper.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginHelper.java	(revision 3335)
@@ -0,0 +1,110 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.swing.JMenu;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.PluginProxy;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginHelper {
+    
+    private static PluginHelper INSTANCE = new PluginHelper();
+    private Map<String, JMenu>menus;
+    private VariableHelper variableHelper;
+    
+    /**
+     * Private constructor
+     */
+    private PluginHelper() {
+        menus = new HashMap<String, JMenu>();
+        registerMenu("file", Main.main.menu.fileMenu);
+        registerMenu("view", Main.main.menu.viewMenu);
+        registerMenu("edit", Main.main.menu.editMenu);
+        registerMenu("tools", Main.main.menu.toolsMenu);
+        registerMenu("connection", Main.main.menu.connectionMenu);
+        registerMenu("layer", Main.main.menu.layerMenu);
+        registerMenu("help", Main.main.menu.helpMenu);
+        
+        variableHelper = new VariableHelper();
+        variableHelper.addAll(Main.pref.getAllPrefix(""));
+        variableHelper.add("josm.user.dir", Main.pref.getPreferencesDir());
+    }
+    
+    /**
+     * Returns the singleton instance of this helper.
+     * @return the singleton instance of this helper.
+     */
+    public static PluginHelper getInstance() {
+        return INSTANCE;
+    }
+    
+    /**
+     * Adds a menu to the main menu of JOSM and registers it under the given id.
+     * @param menuId the id to register the menu, so other plugins can retrieve
+     * the menu with {@link #getMenu(String)}
+     * @param menu the menu to add.
+     */
+    public void addMainMenu(String menuId, JMenu menu) {
+        Main.main.menu.add(menu);
+        registerMenu(menuId, menu);
+    }
+    
+    /**
+     * Register the menu under the given id so other plugins can retrieve
+     * the menu with {@link #getMenu(String)}. 
+     * @param menuId
+     * @param menu
+     */
+    public void registerMenu(String menuId, JMenu menu) {
+        menus.put(menuId, menu);
+    }
+    
+    /**
+     * Returns the main menu that was registered with the given id or <code>null</code> 
+     * if no menu was registered with that id. The default menus have the ids:
+     * <code>file</code>, <code>view</code>, <code>edit</code>, <code>tools</code>,
+     * <code>connection</code>, <code>layer</code>, <code>help</code>,
+     * @return the main menu.
+     */
+    public JMenu getMenu(String menuId) {
+        return menus.get(menuId);
+    }
+
+    /**
+     * @return the variableHelper
+     */
+    public VariableHelper getVariableHelper() {
+        return this.variableHelper;
+    }
+    
+    /**
+     * Returns the plugin with the given id or <code>null</code>
+     * if the plugin is not installed.
+     * @param pluginName the name of the plugin
+     * @returnthe plugin with the given name or <code>null</code>
+     * if the plugin is not installed.
+     */
+    public PluginInformation getPluginInfo(String pluginId) {
+        for(PluginProxy plugin : Main.plugins) {
+            System.out.println("compare id " + pluginId + " with " + plugin.info.name);
+            if(pluginId.equals(plugin.info.name)) {
+                return plugin.info;
+            }
+        }
+        return null;
+    }
+    
+
+    
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerAction.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerAction.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerAction.java	(revision 3335)
@@ -0,0 +1,56 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+import javax.swing.AbstractAction;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginManagerAction extends AbstractAction {
+    
+    /**
+     * Constructor
+     */
+    public PluginManagerAction() {
+        super("pluginmanager"); //, ImageProvider.get("preferences", "plugin"));
+        putValue(AbstractAction.NAME, "Plugin Manager");
+        putValue(AbstractAction.LONG_DESCRIPTION, "Allows to download/install plugins");
+        putValue(AbstractAction.MNEMONIC_KEY, KeyEvent.VK_P);
+        //putValue(AbstractAction.LARGE_ICON_KEY, ImageProvider.get("preferences", "plugin"));
+        putValue(AbstractAction.SMALL_ICON, ImageProvider.get("pluginmanager"));
+    }
+
+    /* (non-Javadoc)
+     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
+     */
+    public void actionPerformed(ActionEvent e) {
+        // show window like eclipse software update window:
+        // left a list of sites, right buttons "add remote site"
+        // list has checkboxes
+        // adding: name and url where site.xml can be found. 
+        // site.xml containing a list of plugin.xml files
+        // plugin info holds name, version and dependencies (including versions)
+        // button next: check for updates and present list of (new/updateable) plugins
+        // button to resolve dependencies automatically
+        // page to download, page to install, page to enable new plugins
+        // finish
+        
+        // plugin.xml holds info
+        // name, version, dependencies, installpath, 
+        
+        // info about other resources: images, log4j.jar, ....
+        // need source url and target dir (relative to .josm? or main app dir (for josm update))
+        // need restart of josm afterwards
+        
+        // pluginmanager could also load the plugins itself, so dependencies could be respected
+    }
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPlugin.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPlugin.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPlugin.java	(revision 3335)
@@ -0,0 +1,28 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import javax.swing.JMenu;
+
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.plugins.Plugin;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginManagerPlugin extends Plugin {
+    
+    public PluginManagerPlugin() {
+//        JMenu menu = PluginHelper.getInstance().getMenu("tools");
+//        menu.addSeparator();
+//        menu.add(new PluginManagerAction());
+    }
+    
+    @Override
+    public PreferenceSetting getPreferenceSetting() {
+        return new PluginManagerPreference();
+    }
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPreference.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPreference.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginManagerPreference.java	(revision 3335)
@@ -0,0 +1,193 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.swing.Box;
+import javax.swing.CellRendererPane;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PreferenceDialog;
+import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
+import org.openstreetmap.josm.tools.GBC;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginManagerPreference implements PreferenceSetting {
+    private String PREF_KEY_REMOTE_SITE_PREFIX = "pluginmanager.site.";
+    private String PREF_KEY_SITE_NAME_SUFFIX = ".name";
+    private String PREF_KEY_SITE_URL_SUFFIX = ".url";
+    private JList siteList;
+    private DefaultListModel siteListModel;
+    
+    protected DefaultListModel createListModel() {
+        Map<String, String> sites = Main.pref.getAllPrefix(PREF_KEY_REMOTE_SITE_PREFIX);
+        if(sites.keySet().size() == 0) {
+            // add default entry (for demonstration purpose)
+            sites.put(PREF_KEY_REMOTE_SITE_PREFIX + "0"+PREF_KEY_SITE_URL_SUFFIX, 
+                "http://www.tegmento.org/~cdaller/josm/");
+        }
+        int siteCount = 0;
+        String name;
+        String url;
+        SiteDescription description;
+        DefaultListModel listModel = new DefaultListModel();
+        while((url = sites.get(PREF_KEY_REMOTE_SITE_PREFIX + siteCount + PREF_KEY_SITE_URL_SUFFIX)) != null) {
+            name = sites.get(PREF_KEY_REMOTE_SITE_PREFIX + siteCount + PREF_KEY_SITE_NAME_SUFFIX);
+            try {
+                description = new SiteDescription(name, url);
+                listModel.addElement(description);
+            } catch (MalformedURLException e) {
+                e.printStackTrace();
+            }
+            ++siteCount;
+        }        
+        return listModel;
+    }
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.josm.gui.preferences.PreferenceSetting#addGui(org.openstreetmap.josm.gui.preferences.PreferenceDialog)
+     */
+    // only in 1.6 allowed @Override
+    public void addGui(final PreferenceDialog gui) {
+        
+        siteListModel = createListModel();
+        siteList = new JList(siteListModel);
+        siteList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+
+        JButton addSite = new JButton(tr("Add Site"));
+        addSite.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                String siteUrl = JOptionPane.showInputDialog(Main.parent, tr("Update Site Url"));
+                if (siteUrl == null)
+                    return;
+                SiteDescription site;
+                try {
+                    if(!siteUrl.endsWith("/")) {
+                        siteUrl = siteUrl + "/";
+                    }
+                  site = new SiteDescription(siteUrl);
+                  siteListModel.addElement(site);
+                } catch(MalformedURLException mue) {
+                    JOptionPane.showMessageDialog(Main.parent, tr("Invalid Url"), tr("Error"), JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                gui.requiresRestart = gui.requiresRestart || false;
+            }
+        });
+
+        JButton deleteSite = new JButton(tr("Delete Site(s)"));
+        deleteSite.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (siteList.getSelectedIndex() == -1)
+                    JOptionPane.showMessageDialog(Main.parent, tr("Please select the site to delete."));
+                else {
+                    int[] selected = siteList.getSelectedIndices();
+                    for (int i = selected.length - 1; i >=0; --i) {                        
+                        siteListModel.removeElementAt(selected[i]);
+                    }
+                    gui.requiresRestart = gui.requiresRestart || false;
+                }
+            }
+        });
+
+        JButton checkSite = new JButton(tr("Check Site(s)"));
+        checkSite.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                if (siteList.getSelectedIndex() == -1)
+                    JOptionPane.showMessageDialog(Main.parent, tr("Please select the site(s) to check for updates."));
+                else {
+                    int[] selected = siteList.getSelectedIndices();
+                    List<SiteDescription> descriptions = new ArrayList<SiteDescription>();
+                    SiteDescription description;
+                    for(int selectedIndex : selected) {
+                        description = (SiteDescription)siteListModel.get(selectedIndex);
+                        descriptions.add(description);
+                        try {
+                            description.loadFromUrl();
+                        } catch (IOException e1) {
+                            e1.printStackTrace();
+                        }
+                    }
+                    PluginUpdateFrame frame = new PluginUpdateFrame(tr("Plugins"), descriptions);
+                    frame.setVisible(true);
+                    gui.requiresRestart = true;
+                }
+            }
+        });
+
+        siteList.setVisibleRowCount(3);
+
+        //schemesList.setToolTipText(tr("The sources (url or filename) of annotation preset definition files. See http://josm.eigenheimstrasse.de/wiki/AnnotationPresets for help."));
+        addSite.setToolTipText(tr("Add a new plugin site."));
+        deleteSite.setToolTipText(tr("Delete the selected site(s) from the list."));
+        checkSite.setToolTipText(tr("Check the selected site(s) for new plugins or updates."));
+
+        gui.map.add(new JLabel(tr("Update Sites")), GBC.eol().insets(0,5,0,0));
+        gui.map.add(new JScrollPane(siteList), GBC.eol().fill(GBC.BOTH));
+        JPanel buttonPanel = new JPanel(new GridBagLayout());
+        gui.map.add(buttonPanel, GBC.eol().fill(GBC.HORIZONTAL));
+        buttonPanel.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL));
+        buttonPanel.add(addSite, GBC.std().insets(0,5,5,0));
+        buttonPanel.add(deleteSite, GBC.std().insets(0,5,5,0));
+        buttonPanel.add(checkSite, GBC.std().insets(0,5,5,0));
+    }
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.josm.gui.preferences.PreferenceSetting#ok()
+     */
+    // only in 1.6 allowed @Override
+    public void ok() {
+        // first remove all old entries:
+        Map<String, String> keys = Main.pref.getAllPrefix(PREF_KEY_REMOTE_SITE_PREFIX);
+        for(String key : keys.keySet()) {
+            Main.pref.put(key, null);
+        }
+        // set all sites into prefs:
+        SiteDescription desc;
+        String key;
+        for(int index = 0; index < siteListModel.getSize(); ++index) {
+            desc = (SiteDescription) siteListModel.elementAt(index);
+            if(desc.getName() != null) {
+                key = PREF_KEY_REMOTE_SITE_PREFIX + index + PREF_KEY_SITE_NAME_SUFFIX;
+                Main.pref.put(key, desc.getName());
+            }
+            key = PREF_KEY_REMOTE_SITE_PREFIX + index + PREF_KEY_SITE_URL_SUFFIX;
+            try {
+                Main.pref.put(key, desc.getUrl().toURI().toASCIIString());
+            } catch (URISyntaxException e) {
+            }
+        }
+    }
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginResource.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginResource.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginResource.java	(revision 3335)
@@ -0,0 +1,118 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginResource {
+    private static final int BUFFER_SIZE = 16384;
+    private URL resourceUrl;
+    private String target;
+    private String errorMessage;
+    private Exception errorException;
+    
+    /**
+     * @return the resourceUrl
+     */
+    public URL getResourceUrl() {
+        return this.resourceUrl;
+    }
+    /**
+     * @param resourceUrl the resourceUrl to set
+     */
+    public void setResourceUrl(URL resourceUrl) {
+        this.resourceUrl = resourceUrl;
+    }
+    /**
+     * @return the target
+     */
+    public String getTarget() {
+        return this.target;
+    }
+    /**
+     * @param target the target to set
+     */
+    public void setTarget(String target) {
+        this.target = target;
+    }
+    
+    /**
+     * @return the errorMessage
+     */
+    public String getErrorMessage() {
+        return this.errorMessage;
+    }
+    /**
+     * @param errorMessage the errorMessage to set
+     */
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+    /**
+     * @return the errorException
+     */
+    public Exception getErrorException() {
+        return this.errorException;
+    }
+    /**
+     * @param errorException the errorException to set
+     */
+    public void setErrorException(Exception errorException) {
+        this.errorException = errorException;
+    }
+    /**
+     * Installs the resource into to the target location. 
+     * @throws IOException if the resource could not be read or written. 
+     */
+    public void install() {
+       File targetFile = new File(target);
+       File parentDir = targetFile.getParentFile();
+       if(!parentDir.exists() && !parentDir.mkdirs()) {
+           errorMessage = "Could not create the target directory: " + parentDir.getAbsolutePath();
+           return;
+       }
+       
+       // copy resource to local filesystem:
+       System.out.println("Install " + resourceUrl + " to " + targetFile);
+       byte[] buffer = new byte[BUFFER_SIZE];
+       int read;
+       InputStream in = null;
+       OutputStream out = null;
+       try {
+           in = resourceUrl.openConnection().getInputStream();
+           out = new BufferedOutputStream(new FileOutputStream(targetFile));
+           while((read = in.read(buffer)) >= 0) {
+               out.write(buffer, 0, read);
+           }
+       } catch(IOException e) {
+           errorMessage = e.getMessage();
+           errorException = e;
+       } finally {
+           try {
+             if(in != null) in.close();
+           } catch(IOException ignore) {}
+           try {
+               if(out != null) out.close();
+             } catch(IOException ignore) {}
+       }
+       
+    }
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return getClass().getSimpleName() + "[url=" + resourceUrl + ", target=" + target + "]";
+    }
+ 
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginTableModel.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginTableModel.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginTableModel.java	(revision 3335)
@@ -0,0 +1,158 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginTableModel extends AbstractTableModel {
+    private List<SiteDescription> sites;
+    private List<PluginDescription> plugins;
+    
+    public PluginTableModel(List<SiteDescription> descriptions) {
+        this.sites = descriptions;
+        update();
+    }
+    
+    private void update() {
+        plugins = new ArrayList<PluginDescription>();
+        for (SiteDescription site : sites) {
+            plugins.addAll(site.getPlugins());
+        }
+    }
+    
+    
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
+     */
+    @Override
+    public boolean isCellEditable(int rowIndex, int columnIndex) {
+        return columnIndex == 0;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.TableModel#getColumnCount()
+     */
+    public int getColumnCount() {
+        return 3;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.TableModel#getRowCount()
+     */
+    public int getRowCount() {
+        return plugins.size();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.TableModel#getValueAt(int, int)
+     */
+    public Object getValueAt(int rowIndex, int columnIndex) {
+        PluginDescription plugin = plugins.get(rowIndex);
+        switch(columnIndex) {
+        case 0: return plugin.isInstall();
+        case 1: return plugin.getName();
+        case 2: if(plugin.getInstalledVersion() != null) {
+            return plugin.getInstalledVersion() + " -> " + plugin.getVersion();
+        } else {
+            return plugin.getVersion();            
+        }
+        default: throw new IllegalArgumentException("Illegal Column Index " + columnIndex);
+        }
+    }
+    
+    
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
+     */
+    @Override
+    public void setValueAt(Object value, int rowIndex, int columnIndex) {
+        if(columnIndex != 0) {
+            return;
+        }
+        PluginDescription plugin = plugins.get(rowIndex);
+        plugin.setInstall(Boolean.TRUE.equals(value));
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.AbstractTableModel#getColumnClass(int)
+     */
+    @Override
+    public Class<?> getColumnClass(int columnIndex) {
+        switch(columnIndex) {
+        case 0: return Boolean.class;
+        case 1:
+        case 2: return Object.class;
+        default: throw new IllegalArgumentException("Illegal Column Index " + columnIndex);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see javax.swing.table.AbstractTableModel#getColumnName(int)
+     */
+    @Override
+    public String getColumnName(int column) {
+        switch(column) {
+        case 0: return "";
+        case 1: return tr("Name");
+        case 2: return tr("Version");
+        default: throw new IllegalArgumentException("Illegal Column Index " + column);
+        }
+    }
+
+    /**
+     * Installs all selected plugins.
+     */
+    public void install() {
+        for(PluginDescription plugin : plugins) {
+            if(plugin.isInstall()) {
+                System.out.println("Installing plugin " + plugin.getName());
+                plugin.install();
+            }
+        }
+        
+    }
+
+    /**
+     * @return the sites
+     */
+    public List<SiteDescription> getSites() {
+        return this.sites;
+    }
+
+    /**
+     * @param sites the sites to set
+     */
+    public void setSites(List<SiteDescription> sites) {
+        this.sites = sites;
+    }
+
+    /**
+     * @return the plugins
+     */
+    public List<PluginDescription> getPlugins() {
+        return this.plugins;
+    }
+
+    /**
+     * @param plugins the plugins to set
+     */
+    public void setPlugins(List<PluginDescription> plugins) {
+        this.plugins = plugins;
+    }
+    
+    
+    
+    
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginUpdateFrame.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginUpdateFrame.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/PluginUpdateFrame.java	(revision 3335)
@@ -0,0 +1,84 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.HeadlessException;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.border.EmptyBorder;
+
+/**
+ * @author cdaller
+ *
+ */
+public class PluginUpdateFrame extends JFrame {
+    private JTabbedPane tabPane;
+    //private ManagePanel manager;
+    private InstallPanel updater;
+    private InstallPanel installer;
+
+
+
+    /**
+     * @param title
+     * @param descriptions 
+     * @throws HeadlessException
+     */
+    public PluginUpdateFrame(String title, List<SiteDescription> descriptions) throws HeadlessException {
+        super(title);
+        init(descriptions);
+    }
+    
+    public void init(List<SiteDescription> descriptions) {
+        
+        /* Setup panes */
+        JPanel content = new JPanel(new BorderLayout(12,12));
+        content.setBorder(new EmptyBorder(12,12,12,12));
+        setContentPane(content);
+
+        tabPane = new JTabbedPane();
+        //tabPane.addTab(tr("Manage"),  manager = new ManagePanel(this));
+        tabPane.addTab(tr("Update"), updater = new InstallPanel(this, descriptions));
+        //tabPane.addTab(tr("Install"),installer = new InstallPanel(this));
+
+        content.add(BorderLayout.CENTER,tabPane);
+        
+        JPanel buttonPannel = new JPanel();
+        // <FIXXME date="23.06.2007" author="cdaller">
+        // TODO i18n!
+        JButton okButton = new JButton(tr("Install"));
+        JButton cancelButton = new JButton(tr("Cancel"));
+        // </FIXXME> 
+        buttonPannel.add(okButton);
+        buttonPannel.add(cancelButton);
+
+        okButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                updater.install();
+                setVisible(false);
+                dispose();
+            }   
+        });
+        
+        cancelButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                setVisible(false);
+                dispose();
+            }   
+        });
+        content.add(BorderLayout.SOUTH,buttonPannel);
+        
+        pack();
+    }
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescription.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescription.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescription.java	(revision 3335)
@@ -0,0 +1,173 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.xml.sax.SAXException;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SiteDescription {
+    public static final String SITE_FILE_NAME = "josm-site.xml";
+    private String name;
+    private URL url;
+    List<PluginDescription>plugins = new ArrayList<PluginDescription>();
+    
+    /**
+     * Default Constructor
+     */
+    public SiteDescription() {
+    }
+
+    /**
+     * @param url
+     * @throws MalformedURLException
+     */
+    public SiteDescription(String url) throws MalformedURLException {
+        if(!url.endsWith("/")) {
+            url = url + "/";
+        }
+        this.url = new URL(url);
+    }
+    
+    /**
+     * @param name
+     * @param url
+     */
+    public SiteDescription(String name, String url) throws MalformedURLException {
+        this(url);
+        this.name = name;
+    }
+    
+
+    /**
+     * @return the name
+     */
+    public String getName() {
+        return this.name;
+    }
+    /**
+     * @param name the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    
+    /**
+     * Load the site description from the url.
+     * @throws IOException 
+     */
+    public void loadFromUrl() throws IOException {
+        URL xmlUrl = new URL(url, SITE_FILE_NAME);
+        System.out.println("loading from url " + xmlUrl);
+        URLConnection connection = xmlUrl.openConnection();
+        // <FIXXME date="20.06.2007" author="cdaller">
+        // TODO check and remember modified since date to compare and not load sites that did not change
+        // connection.getIfModifiedSince()
+        // </FIXXME> 
+        
+        Reader in = new InputStreamReader(connection.getInputStream());
+        SiteDescriptionParser parser = new SiteDescriptionParser(this);
+        try {
+//            VariableHelper varHelper = new VariableHelper();
+//            varHelper.add("josm.user.dir", "${user.home}/.josm");
+//            parser.setVariableHelper(varHelper);
+            parser.setVariableHelper(PluginHelper.getInstance().getVariableHelper());
+            parser.parse(in);
+            //System.out.println("site describes plugins: " + plugins);
+        } catch (SAXException e) {
+            e.printStackTrace();
+        } finally {        
+            in.close();
+        }
+        
+        // check if the plugins are already installed:
+        PluginHelper helper = PluginHelper.getInstance();
+        PluginInformation info;
+        for(PluginDescription plugin : plugins) {
+            info = helper.getPluginInfo(plugin.getId());
+            if(info != null) {
+                if(info.version == null) {
+                    plugin.setInstalledVersion("?");                    
+                } else {
+                    plugin.setInstalledVersion(info.version);
+                }
+            }
+        }
+    }
+    
+    /**
+     * @return the plugins
+     */
+    public List<PluginDescription> getPlugins() {
+        return this.plugins;
+    }
+
+    /**
+     * @param pluginDescription
+     */
+    public void addPlugin(PluginDescription pluginDescription) {
+        plugins.add(pluginDescription);
+    }
+
+    
+    /**
+     * @return the url
+     */
+    public URL getUrl() {
+        return this.url;
+    }
+
+    /**
+     * @param url the url to set
+     */
+    public void setUrl(URL url) {
+        this.url = url;
+    }
+
+    /**
+     * Returns the name if not <code>null</code> or the url (must not be <code>null</code>).
+     * @return the name or the url.
+     */
+    public String getLabelName() {
+        if(name == null) {
+            return url.toString();
+        } else {
+            return name;
+        }
+        
+    }
+    
+    /**
+     * Used by ListCellRenderer, so not only a debug method!
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return getLabelName();
+    }
+    
+    public static void main(String[] args) {
+        try {
+            new SiteDescription("file:site/").loadFromUrl();
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescriptionParser.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescriptionParser.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/SiteDescriptionParser.java	(revision 3335)
@@ -0,0 +1,159 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import uk.co.wilson.xml.MinML2;
+
+/**
+ * @author cdaller
+ *
+ */
+public class SiteDescriptionParser extends MinML2 {
+    private SiteDescription siteDescription;
+    private PluginDescription pluginDescription;
+    private StringBuilder characters;
+    private VariableHelper variableHelper;
+    
+    /**
+     * Constructor using a new SiteDescription object.
+     */
+    public SiteDescriptionParser() {
+        this(new SiteDescription());
+    }
+    /**
+     * Constructor using a description object to fill.
+     * @param siteDescription the object to fill.
+     * @param in the reader to fill the description with. 
+     */
+    public SiteDescriptionParser(SiteDescription siteDescription) {
+        this.siteDescription = siteDescription;
+    }
+    
+    /**
+     * Returns the site description object.
+     * @return the site description object.
+     */
+    public SiteDescription getSiteDescription() {
+        return siteDescription;
+    }
+    /**
+     * @return the variableHelper
+     */
+    public VariableHelper getVariableHelper() {
+        return this.variableHelper;
+    }
+    /**
+     * @param variableHelper the variableHelper to set
+     */
+    public void setVariableHelper(VariableHelper variableHelper) {
+        this.variableHelper = variableHelper;
+    }
+    /* (non-Javadoc)
+     * @see uk.co.wilson.xml.MinML2#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
+     */
+    @Override
+    public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
+    throws SAXException {
+        if("site-name".equals(qName)) {
+                siteDescription.setName(qName);
+        } else if("plugin".equals(qName)) {
+            pluginDescription = new PluginDescription();
+            pluginDescription.setId(atts.getValue("id"));
+            pluginDescription.setVersion(atts.getValue("version"));
+        } else if("resource".equals(qName)) {
+            PluginResource resource = new PluginResource();
+            try {
+                resource.setResourceUrl(new URL(atts.getValue("src")));
+                String target = atts.getValue("target");
+                if(variableHelper != null) {
+                    target = variableHelper.replaceVariables(target);
+                }
+                resource.setTarget(target);
+                if(pluginDescription != null) {
+                    pluginDescription.addPluginResource(resource);
+                } else {
+                    throw new SAXException("Resource was defined outside 'plugin' element!");
+                }
+            } catch (MalformedURLException e) {
+                throw new SAXException(e);
+            }
+        } else if("site".equals(qName)) {
+            if(atts.getIndex("ref") >= 0) {
+                System.out.println("Handling referenced sites...");
+            } else if(!"1.0".equals(atts.getValue("version"))) {
+                throw new SAXException("Unknown version of site description (must be '1.0')!");
+            }
+        } else if("site-info".equals(qName)) {
+        } else if("site-url".equals(qName)) {
+        } else if("site-name".equals(qName)) {
+        } else if("plugins".equals(qName)) {
+        } else if("name".equals(qName)) {
+        } else if("description".equals(qName)) {
+        } else if("resources".equals(qName)) {
+        } else if("sites".equals(qName)) {
+        } else {
+            System.err.println("unknown tag: " + qName);
+        }
+    }
+    
+    /** 
+     * Read characters for description.
+     */
+    @Override public void characters(char[] data, int start, int length) throws org.xml.sax.SAXException {
+        if (characters == null) {
+            characters = new StringBuilder();
+        }
+        characters.append(data, start, length);
+    }
+    
+    /* (non-Javadoc)
+     * @see uk.co.wilson.xml.MinML2#endElement(java.lang.String, java.lang.String, java.lang.String)
+     */
+    @Override
+    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+       if("site-name".equals(qName)) {
+           siteDescription.setName(clearCharacters());
+       } else if("site-url".equals(qName)) {
+               try {
+                siteDescription.setUrl(new URL(clearCharacters()));
+            } catch (MalformedURLException e) {
+                throw new SAXException(e);
+            }
+       } else if("name".equals(qName)) {
+           if(pluginDescription != null) {
+               pluginDescription.setName(clearCharacters());
+           } else {
+               throw new SAXException("'" + qName  + "' element is not inside 'plugin' element!");
+           }
+       } else if("description".equals(qName)) {
+           if(pluginDescription != null) {
+               pluginDescription.setDescription(clearCharacters());
+           } else {
+               throw new SAXException("'" + qName  + "' element is not inside 'plugin' element!");
+           }
+       } else if("plugin".equals(qName)) {
+           siteDescription.addPlugin(pluginDescription);
+           pluginDescription = null;
+       }
+    }
+    
+    /**
+     * Clears the characters and returns the its previous content.
+     * @return
+     */
+    private String clearCharacters() {
+        String chars = characters.toString();
+        characters = new StringBuilder();
+        return chars;
+    }
+    
+    
+
+}
Index: applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/VariableHelper.java
===================================================================
--- applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/VariableHelper.java	(revision 3335)
+++ applications/editors/josm/plugins/pluginmanager/src/at/dallermassl/josm/plugin/pluginmanager/VariableHelper.java	(revision 3335)
@@ -0,0 +1,79 @@
+/**
+ * 
+ */
+package at.dallermassl.josm.plugin.pluginmanager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class that replaces variables in strings with its values. The variables are in the 
+ * form ${name}. Replacement values may be set. As a fallback (if the variable is not found)
+ * the system properties are used. If neither is found, the variable is not replaced.
+ * 
+ * @author cdaller
+ *
+ */
+public class VariableHelper {
+    private Pattern varPattern = Pattern.compile("\\$\\{(.+?)\\}");
+    private Map<String, String> variables;
+    
+    public VariableHelper() {
+        variables = new HashMap<String, String>();
+    }
+    
+    /**
+     * Adds all key/values as variables.
+     * @param pref the values to add.
+     */
+    public void addAll(Map<String,String> values) {
+        variables.putAll(values);
+    }
+    
+    /**
+     * Adds a single key/value pair.
+     * @param key
+     * @param value
+     */
+    public void add(String key, String value) {
+        variables.put(key, value);
+    }
+    
+    /**
+     * Replaces all variable placeholder in the given string with the replacement.
+     * If the variable cannot be found and is not a System.property, the placeholder remains
+     * untouched. The placeholder has the form of "${varname}".
+     * @param value
+     * @return
+     */
+    public String replaceVariables(String value) {
+        StringBuilder source = new StringBuilder(value);
+        Matcher matcher = varPattern.matcher(source);
+        int index = 0;
+        String varName;
+        String replacement;
+        while(matcher.find(index)) {
+            varName = matcher.group(1);
+            replacement = variables.get(varName);
+            if(replacement == null) {
+                replacement = System.getProperty(varName);
+            }
+            if(replacement != null) {
+                source.replace(matcher.start(), matcher.end(), replacement);
+                index = matcher.start();
+            } else {
+                // key not found, move on
+                index = index + 4; // "${x}".length
+            }
+        }
+        return source.toString();
+    }
+    
+    public static void main(String[] args) {
+        VariableHelper helper = new VariableHelper();
+        System.out.println(helper.replaceVariables("abc${java.version}cde${os.name}${user.name}xx${unknoqn}"));
+    }
+
+}
