Index: src/org/openstreetmap/josm/gui/preferences/PluginPreference.java
===================================================================
--- src/org/openstreetmap/josm/gui/preferences/PluginPreference.java	(revision 276)
+++ src/org/openstreetmap/josm/gui/preferences/PluginPreference.java	(revision 277)
@@ -2,68 +2,118 @@
 
 import static org.openstreetmap.josm.tools.I18n.tr;
-
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.Dimension;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.io.File;
+import java.io.FileReader;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.Map.Entry;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
+import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.plugins.PluginDownloader;
 import org.openstreetmap.josm.plugins.PluginException;
 import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.plugins.PluginProxy;
 import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.UrlLabel;
+import org.openstreetmap.josm.tools.XmlObjectParser.Uniform;
 
 public class PluginPreference implements PreferenceSetting {
 
-	private Map<PluginInformation, Boolean> pluginMap;
+	/**
+	 * Only the plugin name, it's jar location and the description.
+	 * In other words, this is the minimal requirement the plugin preference page
+	 * needs to show the plugin as available
+	 * 
+	 * @author imi
+	 */
+	public static class PluginDescription {
+		public String name;
+		public String description;
+		public String resource;
+		public PluginDescription(String name, String description, String resource) {
+			this.name = name;
+			this.description = description;
+			this.resource = resource;
+        }
+		public PluginDescription() {
+        }
+	}
+	
+	private Map<PluginDescription, Boolean> pluginMap;
+	private Box pluginPanel = Box.createVerticalBox();
+	private JPanel plugin;
 
 	public void addGui(final PreferenceDialog gui) {
-		pluginMap = new HashMap<PluginInformation, Boolean>();
-		Box pluginPanel = Box.createVerticalBox();
-		List<PluginInformation> availablePlugins = new LinkedList<PluginInformation>();
-		for (String location : PluginInformation.getPluginLocations()) {
-			File[] pluginFiles = new File(location).listFiles();
-			if (pluginFiles != null) {
-				Arrays.sort(pluginFiles);
-				for (File f : pluginFiles) {
-					if (f.isFile() && f.getName().endsWith(".jar")) {
-						try {
-		                    availablePlugins.add(new PluginInformation(f));
-	                    } catch (PluginException x) {
-	                    }
-					}
-				}
-			}
-		}
-		Collections.sort(availablePlugins, new Comparator<PluginInformation>(){
-			public int compare(PluginInformation o1, PluginInformation o2) {
-	            return o1.name.compareTo(o2.name);
-            }
-		});
-
+		plugin = gui.createPreferenceTab("plugin", tr("Plugins"), tr("Configure available Plugins."));
+		JScrollPane pluginPane = new JScrollPane(pluginPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+		pluginPane.setBorder(null);
+		plugin.add(pluginPane, GBC.eol().fill(GBC.BOTH));
+		plugin.add(GBC.glue(0,10), GBC.eol());
+		JButton morePlugins = new JButton(tr("Get more plugins"));
+		morePlugins.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				int count = PluginDownloader.downloadDescription();
+		    	if (count > 0)
+		    		JOptionPane.showMessageDialog(Main.parent,
+		    				trn("Downloaded plugin information from {0} site",
+		    						"Downloaded plugin information from {0} sites", count, count));
+		    	else
+		    		JOptionPane.showMessageDialog(Main.parent, tr("No plugin information found."));
+		    	refreshPluginPanel(gui);
+            }
+		});
+		plugin.add(morePlugins, GBC.std().insets(0,0,10,0));
+		
+		JButton update = new JButton(tr("Update current"));
+		update.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
+            }
+		});
+		plugin.add(update, GBC.std().insets(0,0,10,0));
+
+		JButton configureSites = new JButton(tr("Configure Plugin Sites"));
+		configureSites.addActionListener(new ActionListener(){
+			public void actionPerformed(ActionEvent e) {
+				JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
+            }
+		});
+		plugin.add(configureSites, GBC.std());
+
+		refreshPluginPanel(gui);
+	}
+
+	private void refreshPluginPanel(final PreferenceDialog gui) {
+	    Collection<PluginDescription> availablePlugins = getAvailablePlugins();
+	    pluginMap = new HashMap<PluginDescription, Boolean>();
+	    pluginPanel.removeAll();
 		Collection<String> enabledPlugins = Arrays.asList(Main.pref.get("plugins").split(","));
-		for (final PluginInformation plugin : availablePlugins) {
+		for (final PluginDescription plugin : availablePlugins) {
 			boolean enabled = enabledPlugins.contains(plugin.name);
 			final JCheckBox pluginCheck = new JCheckBox(plugin.name, enabled);
 			pluginPanel.add(pluginCheck);
 
-			pluginCheck.setToolTipText(plugin.file != null ? plugin.file.getAbsolutePath() : tr("Plugin bundled with JOSM"));
+			pluginCheck.setToolTipText(plugin.resource != null ? plugin.resource : tr("Plugin bundled with JOSM"));
 			JLabel label = new JLabel("<html><i>"+(plugin.description==null?"no description available":plugin.description)+"</i></html>");
 			label.setBorder(BorderFactory.createEmptyBorder(0,20,0,0));
+			label.setMaximumSize(new Dimension(450,1000));
 			pluginPanel.add(label);
 			pluginPanel.add(Box.createVerticalStrut(5));
@@ -77,18 +127,77 @@
 			});
 		}
-		JScrollPane pluginPane = new JScrollPane(pluginPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-		pluginPane.setBorder(null);
-
-		JPanel plugin = gui.createPreferenceTab("plugin", tr("Plugins"), tr("Configure available Plugins."));
-		plugin.add(pluginPane, GBC.eol().fill(GBC.BOTH));
-		plugin.add(GBC.glue(0,10), GBC.eol());
-		plugin.add(new UrlLabel("http://josm.eigenheimstrasse.de/wiki/Plugins", tr("Get more plugins")), GBC.std().fill(GBC.HORIZONTAL));
-	}
+		plugin.updateUI();
+    }
+
+	private Collection<PluginDescription> getAvailablePlugins() {
+		SortedMap<String, PluginDescription> availablePlugins = new TreeMap<String, PluginDescription>(new Comparator<String>(){
+			public int compare(String o1, String o2) {
+	            return o1.compareToIgnoreCase(o2);
+            }
+		});
+		for (String location : PluginInformation.getPluginLocations()) {
+			File[] pluginFiles = new File(location).listFiles();
+			if (pluginFiles != null) {
+				for (File f : pluginFiles) {
+					if (!f.isFile())
+						continue;
+					if (f.getName().endsWith(".jar")) {
+						try {
+							PluginInformation info = new PluginInformation(f);
+		                    availablePlugins.put(info.name, new PluginDescription(info.name, info.description, PluginInformation.getURLString(f.getPath())));
+	                    } catch (PluginException x) {
+	                    }
+					} else if (f.getName().endsWith(".xml")) {
+						try {
+	                        Uniform<PluginDescription> parser = new Uniform<PluginDescription>(new FileReader(f), "plugin", PluginDescription.class);
+	                        for (PluginDescription pd : parser)
+	                        	availablePlugins.put(pd.name, pd);
+                        } catch (Exception e) {
+	                        e.printStackTrace();
+	                        JOptionPane.showMessageDialog(Main.parent, tr("Error reading plugin information file: {0}", f.getName()));
+                        }
+					}
+				}
+			}
+		}
+		for (PluginProxy proxy : Main.plugins)
+			if (!availablePlugins.containsKey(proxy.info.name))
+				availablePlugins.put(proxy.info.name, new PluginDescription(
+						proxy.info.name, 
+						proxy.info.description, 
+						PluginInformation.getURLString(proxy.info.file.getPath())));
+	    return availablePlugins.values();
+    }
 
 	public void ok() {
+		Collection<PluginDescription> toDownload = new LinkedList<PluginDescription>();
+		String msg = "";
+		for (Entry<PluginDescription, Boolean> entry : pluginMap.entrySet()) {
+			if (entry.getValue() && PluginInformation.findPlugin(entry.getKey().name) == null) {
+				toDownload.add(entry.getKey());
+				msg += entry.getKey().name+"\n";
+			}
+		}
+		if (!toDownload.isEmpty()) {
+			int answer = JOptionPane.showConfirmDialog(Main.parent,	
+					tr("Download the following plugins?\n\n{0}", msg), 
+					tr("Download missing plugins"),
+					JOptionPane.YES_NO_OPTION);
+			if (answer != JOptionPane.OK_OPTION)
+				for (PluginDescription pd : toDownload)
+					pluginMap.put(pd, false);
+			else
+				for (PluginDescription pd : toDownload)
+					PluginDownloader.downloadPlugin(pd);
+		}
+
 		String plugins = "";
-		for (Entry<PluginInformation, Boolean> entry : pluginMap.entrySet())
-			if (entry.getValue())
+		for (Entry<PluginDescription, Boolean> entry : pluginMap.entrySet()) {
+			if (entry.getValue()) {
 				plugins += entry.getKey().name + ",";
+				if (PluginInformation.findPlugin(entry.getKey().name) == null)
+					toDownload.add(entry.getKey());
+			}
+		}
 		if (plugins.endsWith(","))
 			plugins = plugins.substring(0, plugins.length()-1);
Index: src/org/openstreetmap/josm/plugins/PluginDownloader.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginDownloader.java	(revision 277)
+++ src/org/openstreetmap/josm/plugins/PluginDownloader.java	(revision 277)
@@ -0,0 +1,93 @@
+/**
+ * 
+ */
+package org.openstreetmap.josm.plugins;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.gui.preferences.PluginPreference.PluginDescription;
+
+public class PluginDownloader {
+	private static final Pattern wiki = Pattern.compile("^</td></tr><tr><td><a class=\"ext-link\" href=\"([^\"]*)\"><span class=\"icon\">([^<]*)</span></a></td><td>[^<]*</td><td>(.*)");
+
+	public static int downloadDescription() {
+		int count = 0;
+		for (String site : Main.pref.get("pluginmanager.sites", "http://josm.openstreetmap.de/wiki/Plugins").split(" ")) {
+			try {
+				BufferedReader r = new BufferedReader(new InputStreamReader(new URL(site).openStream()));
+				CharSequence txt;
+				if (site.toLowerCase().endsWith(".xml"))
+					txt = readXml(r);
+				else
+					txt = readWiki(r);
+				r.close();
+				new File(Main.pref.getPreferencesDir()+"plugins").mkdir();
+				FileWriter out = new FileWriter(Main.pref.getPreferencesDir()+"plugins/site-"+site.replaceAll("[/:\\\\ <>|]", "_")+".xml");
+				out.append(txt);
+				out.close();
+				count++;
+			} catch (IOException x) {
+			}
+		}
+		return count;
+	}
+
+	private static CharSequence readXml(BufferedReader r) throws IOException {
+		StringBuilder b = new StringBuilder();
+		for (String line = r.readLine(); line != null; line = r.readLine())
+			b.append(line+"\n");
+		return b;
+	}
+
+	private static CharSequence readWiki(BufferedReader r) throws IOException {
+		StringBuilder b = new StringBuilder("<plugins>\n");
+		for (String line = r.readLine(); line != null; line = r.readLine()) {
+			Matcher m = wiki.matcher(line);
+			if (!m.matches())
+				continue;
+			b.append("  <plugin>\n");
+			b.append("    <name>"+escape(m.group(2))+"</name>\n");
+			b.append("    <resource>"+escape(m.group(1))+"</resource>\n");
+			b.append("    <description>"+escape(m.group(3))+"</description>\n");
+			b.append("  </plugin>\n");
+		}
+		b.append("</plugins>\n");
+		return b;
+	}
+
+	private static String escape(String s) {
+	    return s.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+    }
+
+	public static void downloadPlugin(PluginDescription pd) {
+		File file = new File(Main.pref.getPreferencesDir()+"plugins/"+pd.name+".jar");
+	    try {
+	        InputStream in = new URL(pd.resource).openStream();
+			OutputStream out = new FileOutputStream(file);
+	        byte[] buffer = new byte[8192];
+	        for (int read = in.read(buffer); read != -1; read = in.read(buffer))
+	        	out.write(buffer, 0, read);
+	        out.close();
+	        in.close();
+        } catch (Exception e) {
+        	if (file.exists())
+        		file.delete();
+        	JOptionPane.showMessageDialog(Main.parent, tr("Could not download plugin: {0} from {1}", pd.name, pd.resource));
+        }
+    }
+}
Index: src/org/openstreetmap/josm/plugins/PluginInformation.java
===================================================================
--- src/org/openstreetmap/josm/plugins/PluginInformation.java	(revision 276)
+++ src/org/openstreetmap/josm/plugins/PluginInformation.java	(revision 277)
@@ -32,4 +32,5 @@
 	public final String author;
 	public final int stage;
+	public final String version;
 	public final List<URL> libraries = new ArrayList<URL>();
 
@@ -64,4 +65,5 @@
 			String stageStr = attr.getValue("Plugin-Stage");
 			stage = stageStr == null ? 50 : Integer.parseInt(stageStr);
+			version = attr.getValue("Plugin-Version");
 			author = attr.getValue("Author");
 			if (file != null)
@@ -118,5 +120,5 @@
 	}
 
-	private String getURLString(String fileName) {
+	public static String getURLString(String fileName) {
 		if (System.getProperty("os.name").startsWith("Windows"))
 			return "file:/"+fileName;
Index: src/org/openstreetmap/josm/tools/XmlObjectParser.java
===================================================================
--- src/org/openstreetmap/josm/tools/XmlObjectParser.java	(revision 276)
+++ src/org/openstreetmap/josm/tools/XmlObjectParser.java	(revision 277)
@@ -66,6 +66,8 @@
 			if (mapping.containsKey(qname) && !mapping.get(qname).onStart)
 				report();
-			else if (characters != null && !current.isEmpty())
+			else if (characters != null && !current.isEmpty()) {
 				setValue(qname, characters);
+				characters = "";
+			}
 		}
 		@Override public void characters(char[] ch, int start, int length) {
