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.IOException;
008import java.io.InputStream;
009import java.io.InputStreamReader;
010import java.nio.charset.StandardCharsets;
011import java.util.LinkedList;
012import java.util.List;
013import java.util.jar.Attributes;
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 attributes
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, Attributes manifest) throws PluginListParseException {
037        try {
038            return new PluginInformation(
039                    manifest,
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        try (BufferedReader r = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
062            String name = null;
063            String url = null;
064            Attributes manifest = new Attributes();
065            for (String line = r.readLine(); line != null; line = r.readLine()) {
066                if (line.startsWith("\t")) {
067                    final String[] keyValue = line.split("\\s*:\\s*", 2);
068                    if (keyValue.length >= 2)
069                        manifest.put(new Attributes.Name(keyValue[0].substring(1)), keyValue[1]);
070                    continue;
071                }
072                addPluginInformation(ret, name, url, manifest);
073                String[] x = line.split(";", -1);
074                if (x.length != 2)
075                    throw new IOException(tr("Illegal entry in plugin list.") + " " + line);
076                name = x[0];
077                url = x[1];
078                manifest = new Attributes();
079            }
080            addPluginInformation(ret, name, url, manifest);
081            return ret;
082        } catch (IOException e) {
083            throw new PluginListParseException(e);
084        }
085    }
086
087    private static void addPluginInformation(List<PluginInformation> ret, String name, String url, Attributes manifest) {
088        try {
089            if (name != null) {
090                PluginInformation info = createInfo(name, url, manifest);
091                for (PluginProxy plugin : PluginHandler.pluginList) {
092                    if (plugin.getPluginInformation().name.equals(info.getName())) {
093                        info.localversion = plugin.getPluginInformation().localversion;
094                    }
095                }
096                ret.add(info);
097            }
098        } catch (PluginListParseException ex) {
099            Logging.error(ex);
100        }
101    }
102
103}