001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.plugins; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.BufferedReader; 007import java.io.ByteArrayInputStream; 008import java.io.IOException; 009import java.io.InputStream; 010import java.io.InputStreamReader; 011import java.nio.charset.StandardCharsets; 012import java.util.LinkedList; 013import java.util.List; 014 015import org.openstreetmap.josm.tools.Logging; 016 017/** 018 * A parser for the plugin list provided by a JOSM Plugin Download Site. 019 * 020 * See <a href="https://josm.openstreetmap.de/plugin">https://josm.openstreetmap.de/plugin</a> 021 * for a sample of the document. The format is a custom format, kind of mix of CSV and RFC822 style 022 * name/value-pairs. 023 * 024 */ 025public class PluginListParser { 026 027 /** 028 * Creates the plugin information object 029 * 030 * @param name the plugin name 031 * @param url the plugin download url 032 * @param manifest the plugin manifest 033 * @return a plugin information object 034 * @throws PluginListParseException if plugin manifest cannot be parsed 035 */ 036 public static PluginInformation createInfo(String name, String url, String manifest) throws PluginListParseException { 037 try { 038 return new PluginInformation( 039 new ByteArrayInputStream(manifest.getBytes(StandardCharsets.UTF_8)), 040 name.substring(0, name.length() - 4), 041 url 042 ); 043 } catch (PluginException e) { 044 throw new PluginListParseException(tr("Failed to create plugin information from manifest for plugin ''{0}''", name), e); 045 } 046 } 047 048 /** 049 * Parses a plugin information document and replies a list of plugin information objects. 050 * 051 * See <a href="https://josm.openstreetmap.de/plugin">https://josm.openstreetmap.de/plugin</a> 052 * for a sample of the document. The format is a custom format, kind of mix of CSV and RFC822 style 053 * name/value-pairs. 054 * 055 * @param in the input stream from which to parse 056 * @return the list of plugin information objects 057 * @throws PluginListParseException if something goes wrong while parsing 058 */ 059 public List<PluginInformation> parse(InputStream in) throws PluginListParseException { 060 List<PluginInformation> ret = new LinkedList<>(); 061 BufferedReader r = null; 062 try { 063 r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 064 String name = null; 065 String url = null; 066 StringBuilder manifest = new StringBuilder(); 067 for (String line = r.readLine(); line != null; line = r.readLine()) { 068 if (line.startsWith("\t")) { 069 line = line.substring(1); 070 /* NOTE: Although manifest specification says line should not be longer than 72 bytes it 071 supports more than 500 bytes and thus even the longest possible 72 character UTF-8, so 072 this code correctly splits the text at 70 characters, not bytes. */ 073 while (line.length() > 70) { 074 manifest.append(line.substring(0, 70)).append('\n'); 075 line = ' ' + line.substring(70); 076 } 077 manifest.append(line).append('\n'); 078 continue; 079 } 080 addPluginInformation(ret, name, url, manifest.toString()); 081 String[] x = line.split(";"); 082 if (x.length != 2) 083 throw new IOException(tr("Illegal entry in plugin list.")); 084 name = x[0]; 085 url = x[1]; 086 manifest = new StringBuilder(); 087 088 } 089 addPluginInformation(ret, name, url, manifest.toString()); 090 return ret; 091 } catch (IOException e) { 092 throw new PluginListParseException(e); 093 } 094 } 095 096 private static void addPluginInformation(List<PluginInformation> ret, String name, String url, String manifest) { 097 try { 098 if (name != null) { 099 PluginInformation info = createInfo(name, url, manifest); 100 for (PluginProxy plugin : PluginHandler.pluginList) { 101 if (plugin.getPluginInformation().name.equals(info.getName())) { 102 info.localversion = plugin.getPluginInformation().localversion; 103 } 104 } 105 ret.add(info); 106 } 107 } catch (PluginListParseException ex) { 108 Logging.error(ex); 109 } 110 } 111 112}