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.") + " " + line);
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}