Ticket #22034: remote-control.3.patch

File remote-control.3.patch, 70.4 KB (added by hiddewie, 4 years ago)

patch v3

  • src/org/openstreetmap/josm/gui/preferences/remotecontrol/RemoteControlPreference.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/gui/preferences/remotecontrol/RemoteControlPreference.java b/src/org/openstreetmap/josm/gui/preferences/remotecontrol/RemoteControlPreference.java
    a b  
    2727import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
    2828import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
    2929import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
     30import org.openstreetmap.josm.io.remotecontrol.RemoteControlHttpServer;
    3031import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
    3132import org.openstreetmap.josm.spi.preferences.Config;
    3233import org.openstreetmap.josm.tools.GBC;
     
    7879        final JLabel portLabel = new JLabel("<html>"
    7980                + tr("JOSM will always listen at <b>port {0}</b> (http) on localhost."
    8081                + "<br>This port is not configurable because it is referenced by external applications talking to JOSM.",
    81                 Config.getPref().get("remote.control.port", "8111")) + "</html>");
     82                RemoteControlHttpServer.PORT.get()) + "</html>");
    8283        portLabel.setFont(portLabel.getFont().deriveFont(Font.PLAIN));
    8384        remote.add(portLabel, GBC.eol().insets(5, 5, 0, 10).fill(GBC.HORIZONTAL));
    8485
  • src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/AddNodeHandler.java
    a b  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import java.awt.Point;
     7import java.util.Arrays;
    78import java.util.Collections;
     9import java.util.List;
    810
    911import org.openstreetmap.josm.actions.AutoScaleAction;
    1012import org.openstreetmap.josm.actions.AutoScaleAction.AutoScaleMode;
     
    1921import org.openstreetmap.josm.gui.util.GuiHelper;
    2022import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
    2123import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     24import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    2225import org.openstreetmap.josm.spi.preferences.Config;
    2326import org.openstreetmap.josm.tools.Logging;
    2427
     
    3235     */
    3336    public static final String command = "add_node";
    3437
     38    private final RequestParameter<Double> latParameter = RequestParameter.mandatory("lat", Double::parseDouble, "number");
     39    private final RequestParameter<Double> lonParameter = RequestParameter.mandatory("lon", Double::parseDouble, "number");
     40
    3541    private double lat;
    3642    private double lon;
    3743
     
    4147    }
    4248
    4349    @Override
    44     public String[] getMandatoryParams() {
    45         return new String[] {"lat", "lon"};
    46     }
    47 
    48     @Override
    49     public String[] getOptionalParams() {
    50         return new String[] {"addtags"};
     50    public List<RequestParameter<?>> getParameters() {
     51        return Arrays.asList(
     52            latParameter,
     53            lonParameter,
     54            addTagsParameter
     55        );
    5156    }
    5257
    5358    @Override
    5459    public String getUsage() {
    55         return "adds a node (given by its latitude and longitude) to the current dataset";
     60        return "Adds a node (given by its latitude and longitude) to the current dataset";
    5661    }
    5762
    5863    @Override
     
    6671    @Override
    6772    public String getPermissionMessage() {
    6873        return tr("Remote Control has been asked to create a new node.") +
    69                 "<br>" + tr("Coordinates: ") + args.get("lat") + ", " + args.get("lon");
     74                "<br>" + tr("Coordinates: ") + latParameter.read(args) + ", " + lonParameter.read(args);
    7075    }
    7176
    7277    @Override
     
    116121    @Override
    117122    protected void validateRequest() throws RequestHandlerBadRequestException {
    118123        try {
    119             lat = Double.parseDouble(args != null ? args.get("lat") : "");
    120             lon = Double.parseDouble(args != null ? args.get("lon") : "");
     124            lat = latParameter.read(args);
     125            lon = lonParameter.read(args);
    121126        } catch (NumberFormatException e) {
    122127            throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e);
    123128        }
     129
    124130        if (MainApplication.getLayerManager().getEditLayer() == null) {
    125131             throw new RequestHandlerBadRequestException(tr("There is no layer opened to add node"));
    126132        }
  • src/org/openstreetmap/josm/io/remotecontrol/handler/AddWayHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/AddWayHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/AddWayHandler.java
    a b  
    2929import org.openstreetmap.josm.gui.util.GuiHelper;
    3030import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
    3131import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     32import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    3233import org.openstreetmap.josm.spi.preferences.Config;
    3334
    3435/**
     
    4142     */
    4243    public static final String command = "add_way";
    4344
     45    private final RequestParameter<String> wayParameter = RequestParameter.mandatory("way");
     46
    4447    private final List<LatLon> allCoordinates = new ArrayList<>();
    4548
    4649    /**
     
    4952    private Map<LatLon, Node> addedNodes;
    5053
    5154    @Override
    52     public String[] getMandatoryParams() {
    53         return new String[]{"way"};
    54     }
    55 
    56     @Override
    57     public String[] getOptionalParams() {
    58         return new String[] {"addtags"};
     55    public List<RequestParameter<?>> getParameters() {
     56        return Arrays.asList(
     57            wayParameter,
     58            addTagsParameter
     59        );
    5960    }
    6061
    6162    @Override
    6263    public String getUsage() {
    63         return "adds a way (given by a semicolon separated sequence of lat,lon pairs) to the current dataset";
     64        return "Adds a way (given by a semicolon separated sequence of lat,lon pairs) to the current dataset";
    6465    }
    6566
    6667    @Override
     
    9192    @Override
    9293    protected void validateRequest() throws RequestHandlerBadRequestException {
    9394        allCoordinates.clear();
    94         for (String coordinatesString : splitArg("way", SPLITTER_SEMIC)) {
     95        for (String coordinatesString : SPLITTER_SEMIC.split(wayParameter.read(args), -1)) {
    9596            String[] coordinates = coordinatesString.split(",\\s*", 2);
    9697            if (coordinates.length < 2) {
    9798                throw new RequestHandlerBadRequestException(
  • src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/FeaturesHandler.java
    a b  
    55
    66import java.util.Arrays;
    77import java.util.Collection;
     8import java.util.List;
    89import java.util.stream.Collectors;
     10import java.util.stream.Stream;
    911
    1012import javax.json.Json;
    1113import javax.json.JsonArray;
     
    1517
    1618import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
    1719import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
     20import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    1821
    1922/**
    2023 * Reports available commands, their parameters and examples
     
    2730     */
    2831    public static final String command = "features";
    2932
     33    private static final RequestParameter<String> jsonpParameter = RequestParameter.optional("jsonp");
     34    private static final RequestParameter<String> queryParameter = RequestParameter.optional("q");
     35
    3036    @Override
    3137    protected void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException {
    32         String q = args.get("q");
     38        String q = queryParameter.read(args);
    3339        Collection<String> handlers = q == null ? null : Arrays.asList(q.split("[,\\s]+", -1));
    3440        content = getHandlersInfoAsJSON(handlers).toString();
    3541        contentType = "application/json";
    36         if (args.containsKey("jsonp")) {
    37             content = args.get("jsonp") + " && " + args.get("jsonp") + '(' + content + ')';
     42        if (jsonpParameter.isPresent(args)) {
     43            content = jsonpParameter.read(args) + " && " + jsonpParameter.read(args) + '(' + content + ')';
    3844        }
    3945    }
    4046
     
    5258        if (handler.getUsage() != null) {
    5359            json.add("usage", handler.getUsage());
    5460        }
    55         json.add("parameters", toJsonArray(handler.getMandatoryParams()));
    56         json.add("optional", toJsonArray(handler.getOptionalParams()));
    57         json.add("examples", toJsonArray(handler.getUsageExamples(handler.getCommand())));
     61        json.add("parameters", toJsonArray(handler.getParameters().stream().filter(RequestParameter::isMandatory).map(RequestParameter::getName)));
     62        json.add("optional", toJsonArray(handler.getParameters().stream().filter(RequestParameter::isOptional).map(RequestParameter::getName)));
     63        json.add("examples", toJsonArray(Arrays.stream(handler.getUsageExamples(handler.getCommand()))));
    5864        return json.build();
    5965    }
    6066
    61     private static JsonArray toJsonArray(String[] strings) {
    62         return Arrays.stream(strings)
     67    private static JsonArray toJsonArray(Stream<String> strings) {
     68        return strings
    6369                .collect(Collectors.collectingAndThen(Collectors.toList(), Json::createArrayBuilder))
    6470                .build();
    6571    }
     
    7581    }
    7682
    7783    @Override
    78     public String[] getMandatoryParams() {
    79         return new String[0];
    80     }
    81 
    82     @Override
    83     public String[] getOptionalParams() {
    84         return new String[]{"jsonp", "q"};
     84    public List<RequestParameter<?>> getParameters() {
     85        return Arrays.asList(
     86            jsonpParameter,
     87            queryParameter
     88        );
    8589    }
    8690
    8791    @Override
     
    9195
    9296    @Override
    9397    public String getUsage() {
    94         return "reports available commands, their parameters and examples";
     98        return "Reports available commands, their parameters and examples";
    9599    }
    96100
    97101    @Override
    98102    public String[] getUsageExamples() {
    99         return new String[] {"/features", "/features?q=import,add_node"};
     103        return new String[] {
     104            "/features",
     105            "/features?q=import,add_node"
     106        };
    100107    }
    101108}
  • src/org/openstreetmap/josm/io/remotecontrol/handler/ImageryHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/ImageryHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/ImageryHandler.java
    a b  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.util.ArrayList;
    67import java.util.Arrays;
    78import java.util.LinkedHashSet;
     9import java.util.List;
    810import java.util.Map;
    911import java.util.Objects;
    1012import java.util.Set;
     13import java.util.stream.Collectors;
    1114
    1215import org.openstreetmap.josm.data.StructUtils;
    1316import org.openstreetmap.josm.data.imagery.ImageryInfo;
     
    1821import org.openstreetmap.josm.gui.layer.ImageryLayer;
    1922import org.openstreetmap.josm.gui.util.GuiHelper;
    2023import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     24import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    2125import org.openstreetmap.josm.tools.CheckParameterUtil;
    2226import org.openstreetmap.josm.tools.Logging;
    2327import org.openstreetmap.josm.tools.Utils;
     
    3337     */
    3438    public static final String command = "imagery";
    3539
     40    private final RequestParameter<String> urlParameter = RequestParameter.optional("url");
     41    private final RequestParameter<String> idParameters = RequestParameter.optional("id");
     42
    3643    @Override
    3744    public String getPermissionMessage() {
    3845        return tr("Remote Control has been asked to load an imagery layer from the following URL:")
     
    4047    }
    4148
    4249    @Override
    43     public String[] getMandatoryParams() {
    44         return new String[0];
    45     }
    46 
    47     @Override
    48     public String[] getOptionalParams() {
    49         Set<String> params = new LinkedHashSet<>();
    50         params.add("url");
    51         params.add("id");
     50    public List<RequestParameter<?>> getParameters() {
    5251        Map<String, String> struct = StructUtils.serializeStruct(new ImageryPreferenceEntry(), ImageryPreferenceEntry.class,
    53                 StructUtils.SerializeOptions.INCLUDE_NULL, StructUtils.SerializeOptions.INCLUDE_DEFAULT);
    54         params.addAll(struct.keySet());
    55         return params.toArray(new String[0]);
     52            StructUtils.SerializeOptions.INCLUDE_NULL, StructUtils.SerializeOptions.INCLUDE_DEFAULT);
     53
     54        List<RequestParameter<?>> imageryPreferenceParameters = struct.keySet().stream()
     55            .map(RequestParameter::optional)
     56            .filter(param -> !param.getName().equals("url") && !param.getName().equals("id"))
     57            .collect(Collectors.toList());
     58
     59        List<RequestParameter<?>> parameters = new ArrayList<>();
     60        parameters.add(urlParameter);
     61        parameters.add(idParameters);
     62        parameters.addAll(imageryPreferenceParameters);
     63        return parameters;
    5664    }
    5765
    5866    @Override
     
    6169    }
    6270
    6371    protected ImageryInfo buildImageryInfo() {
    64         String id = args.get("id");
     72        String id = idParameters.read(args);
    6573        if (id != null) {
    6674            return ImageryLayerInfo.instance.getAllDefaultLayers().stream()
    6775                    .filter(l -> Objects.equals(l.getId(), id))
     
    108116
    109117    @Override
    110118    public String getUsage() {
    111         return "adds an imagery layer (e.g. WMS, TMS)";
     119        return "Adds an imagery layer (e.g. WMS, TMS)";
    112120    }
    113121
    114122    @Override
     
    119127            "/imagery?id=Bing",
    120128            "/imagery?title=osm&type=tms&url=https://a.tile.openstreetmap.org/%7Bzoom%7D/%7Bx%7D/%7By%7D.png",
    121129            "/imagery?title=landsat&type=wms&url=http://irs.gis-lab.info/?" +
    122                     "layers=landsat&SRS=%7Bproj%7D&WIDTH=%7Bwidth%7D&HEIGHT=%7Bheight%7D&BBOX=%7Bbbox%7D",
    123             "/imagery?title=...&type={"+types+"}&url=....[&cookies=...][&min_zoom=...][&max_zoom=...]"
    124             };
     130                "layers=landsat&SRS=%7Bproj%7D&WIDTH=%7Bwidth%7D&HEIGHT=%7Bheight%7D&BBOX=%7Bbbox%7D",
     131            "/imagery?title=...&type={" + types + "}&url=....[&cookies=...][&min_zoom=...][&max_zoom=...]"
     132        };
    125133    }
    126134}
  • src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/ImportHandler.java
    a b  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.lang.reflect.Array;
    67import java.net.MalformedURLException;
    78import java.net.URL;
     9import java.util.Arrays;
    810import java.util.Collection;
     11import java.util.Collections;
    912import java.util.LinkedHashSet;
     13import java.util.List;
    1014import java.util.Set;
    1115
    1216import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
    1317import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
    1418import org.openstreetmap.josm.gui.MainApplication;
    1519import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     20import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    1621import org.openstreetmap.josm.spi.preferences.Config;
    1722import org.openstreetmap.josm.tools.Logging;
    1823import org.openstreetmap.josm.tools.Utils;
     
    2732     */
    2833    public static final String command = "import";
    2934
     35    private final RequestParameter<String> urlParameter = RequestParameter.mandatory("url");
     36
    3037    private URL url;
    3138    private Collection<DownloadTask> suitableDownloadTasks;
    3239
     
    4855                    task.loadUrl(getDownloadParams(), url.toExternalForm(), null);
    4956                }
    5057            }
    51             LoadAndZoomHandler.parseChangesetTags(args);
     58            LoadAndZoomHandler.parseChangesetTags(LoadAndZoomHandler.changesetTagsParameter, args);
    5259        } catch (RuntimeException ex) { // NOPMD
    5360            Logging.warn("RemoteControl: Error parsing import remote control request:");
    5461            Logging.error(ex);
     
    5764    }
    5865
    5966    @Override
    60     public String[] getMandatoryParams() {
    61         return new String[]{"url"};
    62     }
    63 
    64     @Override
    65     public String[] getOptionalParams() {
    66         return new String[] {"new_layer", "layer_name", "layer_locked", "download_policy", "upload_policy", "changeset_tags"};
     67    public List<RequestParameter<?>> getParameters() {
     68        return Arrays.asList(
     69            urlParameter,
     70            newLayerParameter,
     71            layerNameParameter,
     72            layerLockedParameter,
     73            downloadPolicyParameter,
     74            uploadPolicyParameter,
     75            LoadAndZoomHandler.changesetTagsParameter
     76        );
    6777    }
    6878
    6979    @Override
    7080    public String getUsage() {
    71         return "downloads the specified OSM file and adds it to the current data set";
     81        return "Downloads the specified OSM file and adds it to the current data set";
    7282    }
    7383
    7484    @Override
    7585    public String[] getUsageExamples() {
    76         return new String[] {"/import?url=" + Utils.encodeUrl(
    77                 Config.getUrls().getJOSMWebsite()+"/browser/josm/trunk/nodist/data/direction-arrows.osm?format=txt")};
     86        return new String[] {
     87            "/import?url=" + Utils.encodeUrl(
     88                Config.getUrls().getJOSMWebsite()+"/browser/josm/trunk/nodist/data/direction-arrows.osm?format=txt")
     89        };
    7890    }
    7991
    8092    @Override
     
    102114    @Override
    103115    protected void validateRequest() throws RequestHandlerBadRequestException {
    104116        validateDownloadParams();
    105         String urlString = args != null ? args.get("url") : null;
     117        String urlString = urlParameter.read(args);
    106118        if (Config.getPref().getBoolean("remotecontrol.importhandler.fix_url_query", true)) {
    107119            urlString = Utils.fixURLQuery(urlString);
    108120        }
  • src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadAndZoomHandler.java
    a b  
    99import java.util.Collection;
    1010import java.util.Collections;
    1111import java.util.LinkedHashSet;
     12import java.util.List;
    1213import java.util.Map;
    1314import java.util.Set;
    1415import java.util.concurrent.ExecutionException;
     
    4243import org.openstreetmap.josm.io.OsmTransferException;
    4344import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
    4445import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     46import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    4547import org.openstreetmap.josm.tools.Logging;
    4648import org.openstreetmap.josm.tools.SubclassFilteredCollection;
    4749import org.openstreetmap.josm.tools.Utils;
     
    6365    public static final String command2 = "zoom";
    6466    private static final String CURRENT_SELECTION = "currentselection";
    6567
     68    private final RequestParameter<Double> bottomParameter = RequestParameter.mandatory("bottom", Double::parseDouble, "number");
     69    private final RequestParameter<Double> topParameter = RequestParameter.mandatory("top", Double::parseDouble, "number");
     70    private final RequestParameter<Double> leftParameter = RequestParameter.mandatory("left", Double::parseDouble, "number");
     71    private final RequestParameter<Double> rightParameter = RequestParameter.mandatory("right", Double::parseDouble, "number");
     72    private final RequestParameter<String> selectParameter = RequestParameter.optional("select");
     73    private final RequestParameter<String> searchParameter = RequestParameter.optional("search");
     74    private final RequestParameter<String> zoomModeParameter = RequestParameter.optional("zoom_mode");
     75    private final RequestParameter<String> changesetCommentParameter = RequestParameter.optional("changeset_comment");
     76    private final RequestParameter<String> changesetSourceParameter = RequestParameter.optional("changeset_source");
     77    private final RequestParameter<String> changesetHashtagsParameter = RequestParameter.optional("changeset_hashtags");
     78    public static final RequestParameter<String> changesetTagsParameter = RequestParameter.optional("changeset_tags");
     79
    6680    // Mandatory arguments
    6781    private double minlat;
    6882    private double maxlat;
     
    7892    public String getPermissionMessage() {
    7993        String msg = tr("Remote Control has been asked to load data from the API.") +
    8094                "<br>" + tr("Bounding box: ") + new BBox(minlon, minlat, maxlon, maxlat).toStringCSV(", ");
    81         if (args.containsKey("select") && !toSelect.isEmpty()) {
     95        if (selectParameter.isPresent(args) && !toSelect.isEmpty()) {
    8296            msg += "<br>" + tr("Selection: {0}", toSelect.size());
    8397        }
    8498        return msg;
    8599    }
    86100
    87101    @Override
    88     public String[] getMandatoryParams() {
    89         return new String[] {"bottom", "top", "left", "right"};
    90     }
    91 
    92     @Override
    93     public String[] getOptionalParams() {
    94         return new String[] {"new_layer", "layer_name", "addtags", "select", "zoom_mode",
    95                 "changeset_comment", "changeset_source", "changeset_hashtags", "changeset_tags",
    96                 "search", "layer_locked", "download_policy", "upload_policy"};
     102    public List<RequestParameter<?>> getParameters() {
     103        return Arrays.asList(
     104            bottomParameter,
     105            topParameter,
     106            leftParameter,
     107            rightParameter,
     108            newLayerParameter,
     109            layerNameParameter,
     110            addTagsParameter,
     111            selectParameter,
     112            zoomModeParameter,
     113            changesetCommentParameter,
     114            changesetSourceParameter,
     115            changesetHashtagsParameter,
     116            changesetTagsParameter,
     117            searchParameter,
     118            layerLockedParameter,
     119            downloadPolicyParameter,
     120            uploadPolicyParameter
     121        );
    97122    }
    98123
    99124    @Override
    100125    public String getUsage() {
    101         return "download a bounding box from the API, zoom to the downloaded area and optionally select one or more objects";
     126        return "Download a bounding box from the API, zoom to the downloaded area and optionally select one or more objects";
    102127    }
    103128
    104129    @Override
     
    110135    public String[] getUsageExamples(String cmd) {
    111136        if (command.equals(cmd)) {
    112137            return new String[] {
    113                     "/load_and_zoom?addtags=wikipedia:de=Wei%C3%9Fe_Gasse|maxspeed=5&select=way23071688,way23076176,way23076177," +
    114                             "&left=13.740&right=13.741&top=51.05&bottom=51.049",
    115                     "/load_and_zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999&new_layer=true"};
     138                "/load_and_zoom?addtags=wikipedia:de=Wei%C3%9Fe_Gasse|maxspeed=5&select=way23071688,way23076176,way23076177," +
     139                    "&left=13.740&right=13.741&top=51.05&bottom=51.049",
     140                "/load_and_zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999&new_layer=true"
     141            };
    116142        } else {
    117143            return new String[] {
    118             "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999",
    119             "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=highway+OR+railway",
    120             "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=" + CURRENT_SELECTION + "&addtags=foo=bar",
     144                "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&select=node413602999",
     145                "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=highway+OR+railway",
     146                "/zoom?left=8.19&right=8.20&top=48.605&bottom=48.590&search=" + CURRENT_SELECTION + "&addtags=foo=bar"
    121147            };
    122148        }
    123149    }
     
    185211        /**
    186212         * deselect objects if parameter addtags given
    187213         */
    188         if (args.containsKey("addtags") && !isKeepingCurrentSelection) {
     214        if (addTagsParameter.isPresent(args) && !isKeepingCurrentSelection) {
    189215            GuiHelper.executeByMainWorkerInEDT(() -> {
    190216                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    191217                if (ds == null) // e.g. download failed
     
    196222
    197223        final Collection<OsmPrimitive> forTagAdd = new LinkedHashSet<>();
    198224        final Bounds bbox = new Bounds(minlat, minlon, maxlat, maxlon);
    199         if (args.containsKey("select") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
     225        if (selectParameter.isPresent(args) && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
    200226            // select objects after downloading, zoom to selection.
    201227            GuiHelper.executeByMainWorkerInEDT(() -> {
    202228                Set<OsmPrimitive> newSel = new LinkedHashSet<>();
     
    225251                    map.relationListDialog.selectRelations(Utils.filteredCollection(newSel, Relation.class));
    226252                }
    227253            });
    228         } else if (args.containsKey("search") && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
     254        } else if (selectParameter.isPresent(args) && PermissionPrefWithDefault.CHANGE_SELECTION.isAllowed()) {
    229255            try {
    230                 final SearchCompiler.Match search = SearchCompiler.compile(args.get("search"));
     256                final SearchCompiler.Match search = SearchCompiler.compile(selectParameter.read(args));
    231257                MainApplication.worker.submit(() -> {
    232258                    final DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    233259                    final Collection<OsmPrimitive> filteredPrimitives = SubclassFilteredCollection.filter(ds.allPrimitives(), search);
     
    245271        }
    246272
    247273        // This comes before the other changeset tags, so that they can be overridden
    248         parseChangesetTags(args);
     274        parseChangesetTags(changesetTagsParameter, args);
    249275
    250276        // add changeset tags after download if necessary
    251         if (args.containsKey("changeset_comment") || args.containsKey("changeset_source") || args.containsKey("changeset_hashtags")) {
     277        if (changesetCommentParameter.isPresent(args) || changesetSourceParameter.isPresent(args) || changesetHashtagsParameter.isPresent(args)) {
    252278            MainApplication.worker.submit(() -> {
    253279                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    254280                if (ds != null) {
    255                     for (String tag : Arrays.asList("changeset_comment", "changeset_source", "changeset_hashtags")) {
    256                         if (args.containsKey(tag)) {
    257                             final String tagKey = tag.substring("changeset_".length());
    258                             final String value = args.get(tag);
    259                             if (!Utils.isStripEmpty(value)) {
    260                                 ds.addChangeSetTag(tagKey, value);
    261                             } else {
    262                                 ds.addChangeSetTag(tagKey, null);
    263                             }
    264                         }
     281                    if (changesetCommentParameter.isPresent(args)) {
     282                        ds.addChangeSetTag("comment", changesetCommentParameter.read(args));
     283                    }
     284                    if (changesetSourceParameter.isPresent(args)) {
     285                        ds.addChangeSetTag("source", changesetSourceParameter.read(args));
     286                    }
     287                    if (changesetHashtagsParameter.isPresent(args)) {
     288                        ds.addChangeSetTag("hashtags", changesetHashtagsParameter.read(args));
    265289                    }
    266290                }
    267291            });
    268292        }
    269293
    270294        // add tags to objects
    271         if (args.containsKey("addtags")) {
     295        if (addTagsParameter.isPresent(args)) {
    272296            // needs to run in EDT since forTagAdd is updated in EDT as well
    273297            GuiHelper.executeByMainWorkerInEDT(() -> {
    274298                if (!forTagAdd.isEmpty()) {
     
    288312        }
    289313    }
    290314
    291     static void parseChangesetTags(Map<String, String> args) {
    292         if (args.containsKey("changeset_tags")) {
     315    static void parseChangesetTags(RequestParameter<String> changesetTagsParameter, Map<String, String> args) {
     316        if (changesetTagsParameter.isPresent(args)) {
    293317            MainApplication.worker.submit(() -> {
    294318                DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    295319                if (ds != null) {
    296                     AddTagsDialog.parseUrlTagsToKeyValues(args.get("changeset_tags")).forEach(ds::addChangeSetTag);
     320                    AddTagsDialog.parseUrlTagsToKeyValues(changesetTagsParameter.read(args)).forEach(ds::addChangeSetTag);
    297321                }
    298322            });
    299323        }
     
    304328            return;
    305329        }
    306330        // zoom_mode=(download|selection), defaults to selection
    307         if (!"download".equals(args.get("zoom_mode")) && !primitives.isEmpty()) {
     331        if (!"download".equals(zoomModeParameter.read(args)) && !primitives.isEmpty()) {
    308332            AutoScaleAction.autoScale(AutoScaleMode.SELECTION);
    309333        } else if (MainApplication.isDisplayingMapView()) {
    310334            // make sure this isn't called unless there *is* a MapView
     
    330354        minlon = 0;
    331355        maxlon = 0;
    332356        try {
    333             minlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("bottom") : ""));
    334             maxlat = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("top") : ""));
    335             minlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("left") : ""));
    336             maxlon = LatLon.roundToOsmPrecision(Double.parseDouble(args != null ? args.get("right") : ""));
     357            minlat = LatLon.roundToOsmPrecision(bottomParameter.read(args));
     358            maxlat = LatLon.roundToOsmPrecision(topParameter.read(args));
     359            minlon = LatLon.roundToOsmPrecision(leftParameter.read(args));
     360            maxlon = LatLon.roundToOsmPrecision(rightParameter.read(args));
    337361        } catch (NumberFormatException e) {
    338362            throw new RequestHandlerBadRequestException("NumberFormatException ("+e.getMessage()+')', e);
    339363        }
     
    352376        }
    353377
    354378        // Process optional argument 'select'
    355         if (args != null && args.containsKey("select")) {
     379        if (args != null && selectParameter.isPresent(args)) {
    356380            toSelect.clear();
    357             for (String item : args.get("select").split(",", -1)) {
     381            for (String item : selectParameter.read(args).split(",", -1)) {
    358382                if (!item.isEmpty()) {
    359383                    if (CURRENT_SELECTION.equalsIgnoreCase(item)) {
    360384                        isKeepingCurrentSelection = true;
  • src/org/openstreetmap/josm/io/remotecontrol/handler/LoadDataHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadDataHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadDataHandler.java
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.io.remotecontrol.handler;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
    6 import java.io.ByteArrayInputStream;
    7 import java.nio.charset.StandardCharsets;
    8 
    94import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
    105import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
    116import org.openstreetmap.josm.data.osm.DataSet;
     
    138import org.openstreetmap.josm.io.IllegalDataException;
    149import org.openstreetmap.josm.io.OsmReader;
    1510import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     11import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    1612import org.openstreetmap.josm.tools.Utils;
    1713
     14import java.io.ByteArrayInputStream;
     15import java.nio.charset.StandardCharsets;
     16import java.util.Arrays;
     17import java.util.Collections;
     18import java.util.List;
     19
     20import static org.openstreetmap.josm.tools.I18n.tr;
     21
    1822/**
    1923 * Handler to load data directly from the URL.
    2024 * @since 7636
     
    2832     */
    2933    public static final String command = "load_data";
    3034
     35    private final RequestParameter<String> dataParameter = RequestParameter.mandatory("data");
     36    private final RequestParameter<String> mimeTypeParameter = RequestParameter.optional("mime_type", () -> OSM_MIME_TYPE);
     37
    3138    /**
    3239     * Holds the data input string
    3340     */
     
    4047
    4148    @Override
    4249    protected void handleRequest() throws RequestHandlerErrorException {
    43         MainApplication.worker.submit(new LoadDataTask(getDownloadParams(), dataSet, args.get("layer_name")));
     50        MainApplication.worker.submit(new LoadDataTask(getDownloadParams(), dataSet, layerNameParameter.read(args)));
    4451    }
    4552
    4653    @Override
    47     public String[] getMandatoryParams() {
    48         return new String[]{"data"};
    49     }
    50 
    51     @Override
    52     public String[] getOptionalParams() {
    53         return new String[] {"new_layer", "mime_type", "layer_name", "layer_locked", "download_policy", "upload_policy"};
     54    public List<RequestParameter<?>> getParameters() {
     55        return Arrays.asList(
     56            dataParameter,
     57            mimeTypeParameter,
     58            layerNameParameter,
     59            layerLockedParameter,
     60            downloadPolicyParameter,
     61            uploadPolicyParameter
     62        );
    5463    }
    5564
    5665    @Override
     
    6069
    6170    @Override
    6271    public String[] getUsageExamples() {
    63         return new String[]{
    64                 "/load_data?layer_name=extra_layer&new_layer=true&data=" +
    65                     Utils.encodeUrl("<osm version='0.6'><node id='-1' lat='1' lon='2' /></osm>")};
     72        return new String[] {
     73            "/load_data?layer_name=extra_layer&new_layer=true&data=" +
     74                Utils.encodeUrl("<osm version='0.6'><node id='-1' lat='1' lon='2' /></osm>")
     75        };
    6676    }
    6777
    6878    @Override
     
    8090    @Override
    8191    protected void validateRequest() throws RequestHandlerBadRequestException {
    8292        validateDownloadParams();
    83         this.data = args.get("data");
     93        this.data = dataParameter.read(args);
    8494        /**
    8595         * Holds the mime type. Currently only OSM_MIME_TYPE is supported
    8696         * But it could be extended to text/csv, application/gpx+xml, ... or even binary encoded data
    8797         */
    88         final String mimeType = Utils.firstNonNull(args.get("mime_type"), OSM_MIME_TYPE);
     98        final String mimeType = mimeTypeParameter.read(args);
    8999        try {
    90100            if (OSM_MIME_TYPE.equals(mimeType)) {
    91101                final ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
  • src/org/openstreetmap/josm/io/remotecontrol/handler/LoadObjectHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadObjectHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/LoadObjectHandler.java
    a b  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.util.Arrays;
    67import java.util.LinkedList;
    78import java.util.List;
    89import java.util.concurrent.ExecutionException;
     
    1819import org.openstreetmap.josm.gui.util.GuiHelper;
    1920import org.openstreetmap.josm.io.remotecontrol.AddTagsDialog;
    2021import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     22import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    2123import org.openstreetmap.josm.tools.Logging;
    2224
    2325/**
     
    3436
    3537    private final List<PrimitiveId> ps = new LinkedList<>();
    3638
     39    private final RequestParameter<String> objectsParameter = RequestParameter.mandatory("objects");
     40    private final RequestParameter<Boolean> relationMembersParameter = RequestParameter.optional("relation_members", Boolean::parseBoolean, () -> false, "boolean");
     41    private final RequestParameter<Boolean> referrersParameter = RequestParameter.optional("referrers", Boolean::parseBoolean, () -> false, "boolean");
     42
    3743    @Override
    38     public String[] getMandatoryParams() {
    39         return new String[]{"objects"};
    40     }
    41 
    42     @Override
    43     public String[] getOptionalParams() {
    44         return new String[] {"new_layer", "layer_name", "layer_locked", "download_policy", "upload_policy",
    45                 "addtags", "relation_members", "referrers"};
     44    public List<RequestParameter<?>> getParameters() {
     45        return Arrays.asList(
     46            objectsParameter,
     47            layerNameParameter,
     48            layerLockedParameter,
     49            downloadPolicyParameter,
     50            uploadPolicyParameter,
     51            addTagsParameter,
     52            relationMembersParameter,
     53            referrersParameter
     54        );
    4655    }
    4756
    4857    @Override
    4958    public String getUsage() {
    50         return "downloads the specified objects from the server";
     59        return "Downloads the specified objects from the server";
    5160    }
    5261
    5362    @Override
    5463    public String[] getUsageExamples() {
    55         return new String[] {"/load_object?new_layer=true&objects=w106159509",
     64        return new String[]{
     65            "/load_object?new_layer=true&objects=w106159509",
    5666            "/load_object?new_layer=true&objects=r2263653&relation_members=true",
    5767            "/load_object?objects=n100000&referrers=false"
    5868        };
     
    6474            Logging.info("RemoteControl: download forbidden by preferences");
    6575        }
    6676        if (!ps.isEmpty()) {
    67             final boolean newLayer = getDownloadParams().isNewLayer();
    68             final boolean relationMembers = Boolean.parseBoolean(args.get("relation_members"));
    69             final boolean referrers = Boolean.parseBoolean(args.get("referrers"));
     77            final boolean newLayer = newLayerParameter.read(args);
     78            final boolean relationMembers = relationMembersParameter.read(args);
     79            final boolean referrers = referrersParameter.read(args);
    7080            final DownloadPrimitivesWithReferrersTask task = new DownloadPrimitivesWithReferrersTask(
    71                     newLayer, ps, referrers, relationMembers, args.get("layer_name"), null);
     81                    newLayer, ps, referrers, relationMembers, layerNameParameter.read(args), null);
    7282            try {
    7383                MainApplication.worker.submit(task).get(OSM_DOWNLOAD_TIMEOUT.get(), TimeUnit.SECONDS);
    7484            } catch (InterruptedException | ExecutionException | TimeoutException e) {
     
    100110    protected void validateRequest() throws RequestHandlerBadRequestException {
    101111        validateDownloadParams();
    102112        ps.clear();
    103         for (String i : splitArg("objects", SPLITTER_COMMA)) {
     113        for (String i : SPLITTER_COMMA.split(objectsParameter.read(args), -1)) {
    104114            if (!i.isEmpty()) {
    105115                try {
    106116                    ps.add(SimplePrimitiveId.fromString(i));
  • src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenApiHandler.java
    a b  
    55import static org.openstreetmap.josm.tools.I18n.tr;
    66
    77import java.io.StringWriter;
    8 import java.util.Arrays;
    9 import java.util.stream.Stream;
     8import java.util.Collections;
     9import java.util.List;
    1010
    1111import javax.json.Json;
    1212import javax.json.JsonArrayBuilder;
     
    1515import org.openstreetmap.josm.data.preferences.JosmUrls;
    1616import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
    1717import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
     18import org.openstreetmap.josm.io.remotecontrol.RemoteControlHttpServer;
    1819import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
     20import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    1921import org.openstreetmap.josm.tools.Utils;
    2022
    2123/**
     
    2830     */
    2931    public static final String command = "openapi.json";
    3032
     33    /**
     34     * OpenAPI description.
     35     */
     36    public static final String DESCRIPTION = "The Remote control API for JOSM.\n\nAllows using GET HTTP requests with query parameters to invoke actions in JOSM.";
     37
    3138    @Override
    3239    protected void handleRequest() {
    3340        JsonObjectBuilder openapi = getOpenApi();
     
    3946
    4047    private JsonObjectBuilder getOpenApi() {
    4148        return Json.createObjectBuilder()
    42                 .add("openapi", "3.0.0")
    43                 .add("info", Json.createObjectBuilder()
    44                         .add("title", RequestProcessor.JOSM_REMOTE_CONTROL)
    45                         .add("version", RemoteControl.getVersion())
    46                         .add("contact", Json.createObjectBuilder()
    47                                 .add("name", "JOSM")
    48                                 .add("url", JosmUrls.getInstance().getJOSMWebsite())))
    49                 .add("servers", Json.createArrayBuilder()
    50                         .add(Json.createObjectBuilder().add("url", "http://localhost:8111/")))
    51                 .add("paths", getHandlers());
     49                .add("openapi", "3.0.3")
     50                .add("info", getInfo())
     51                .add("servers", getServers())
     52                .add("paths", getHandlers());
     53    }
     54
     55    private JsonObjectBuilder getInfo() {
     56        return Json.createObjectBuilder()
     57            .add("title", RequestProcessor.JOSM_REMOTE_CONTROL)
     58            .add("description", DESCRIPTION)
     59            .add("version", RemoteControl.getVersion())
     60            .add("contact", getContact());
     61    }
     62
     63    private JsonObjectBuilder getContact() {
     64        return Json.createObjectBuilder()
     65            .add("name", "JOSM")
     66            .add("url", JosmUrls.getInstance().getJOSMWebsite());
     67    }
     68
     69    private JsonArrayBuilder getServers() {
     70        return Json.createArrayBuilder()
     71            .add(Json.createObjectBuilder().add("url", String.format("http://localhost:%s/", RemoteControlHttpServer.PORT.get())));
    5272    }
    5373
    5474    private JsonObjectBuilder getHandlers() {
     
    5979    }
    6080
    6181    private JsonObjectBuilder getHandler(RequestHandler handler) {
     82        return Json.createObjectBuilder()
     83            .add("get", getOperation(handler));
     84    }
     85
     86    private JsonObjectBuilder getOperation(RequestHandler handler) {
     87        return Json.createObjectBuilder()
     88            .add("summary", getSummary(handler))
     89            .add("description", getDescription(handler))
     90            .add("operationId", handler.getCommand())
     91            .add("parameters", getParameters(handler))
     92            .add("responses", operationResponses());
     93    }
     94
     95    private JsonObjectBuilder operationResponses() {
     96        return Json.createObjectBuilder()
     97            .add("200", Json.createObjectBuilder().add("description", "Successful operation"))
     98            .add("400", Json.createObjectBuilder().add("description", "Missing required parameters or bad request format"))
     99            .add("403", Json.createObjectBuilder().add("description", "Action is not permitted"))
     100            .add("500", Json.createObjectBuilder().add("description", "Internal server error"))
     101            .add("502", Json.createObjectBuilder().add("description", "Bad gateway (upstream)"));
     102    }
     103
     104    private JsonArrayBuilder getParameters(RequestHandler handler) {
    62105        JsonArrayBuilder parameters = Json.createArrayBuilder();
    63         Stream.concat(
    64                 Arrays.stream(handler.getMandatoryParams()),
    65                 Arrays.stream(handler.getOptionalParams())
    66         ).distinct().map(param -> Json.createObjectBuilder()
    67                 .add("name", param)
     106
     107        handler.getParameters()
     108            .stream()
     109            .map(param -> Json.createObjectBuilder()
     110                .add("name", param.getName())
    68111                .add("in", "query")
    69                 .add("required", Arrays.asList(handler.getMandatoryParams()).contains(param))
    70                 .add("schema", Json.createObjectBuilder().add("type", "string")) // TODO fix type
    71         ).forEach(parameters::add);
    72         return Json.createObjectBuilder().add("get", Json.createObjectBuilder()
    73                 .add("description", getDescription(handler))
    74                 .add("operationId", handler.getCommand())
    75                 .add("parameters", parameters)
    76                 .add("responses", Json.createObjectBuilder()
    77                         .add("200", Json.createObjectBuilder().add("description", "successful operation")))
    78         );
     112                .add("required", param.isMandatory())
     113                .add("schema", Json.createObjectBuilder().add("type", param.getOpenapiType()))
     114            )
     115            .forEach(parameters::add);
     116
     117        return parameters;
     118    }
     119
     120    private String getSummary(RequestHandler handler) {
     121        return Utils.firstNonNull(handler.getUsage(), String.format("Command %s", handler.getCommand()));
    79122    }
    80123
    81124    private String getDescription(RequestHandler handler) {
     
    90133
    91134    @Override
    92135    public String[] getUsageExamples() {
    93         return new String[]{"https://petstore.swagger.io/?url=http://localhost:8111/openapi.json", "https://swagger.io/specification/"};
     136        return new String[]{
     137            "https://petstore.swagger.io/?url=http://localhost:8111/openapi.json",
     138            "https://swagger.io/specification/"
     139        };
    94140    }
    95141
    96142    @Override
     
    104150    }
    105151
    106152    @Override
    107     public String[] getMandatoryParams() {
    108         return new String[0];
     153    public List<RequestParameter<?>> getParameters() {
     154        return Collections.emptyList();
    109155    }
    110156
    111157    @Override
  • src/org/openstreetmap/josm/io/remotecontrol/handler/OpenFileHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenFileHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/OpenFileHandler.java
    a b  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import java.io.File;
    7 import java.util.Arrays;
     7import java.util.Collections;
    88import java.util.EnumSet;
     9import java.util.List;
    910
    1011import org.openstreetmap.josm.actions.OpenFileAction;
    1112import org.openstreetmap.josm.gui.io.importexport.Options;
    1213import org.openstreetmap.josm.gui.util.GuiHelper;
    1314import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     15import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    1416
    1517/**
    1618 * Opens a local file
     
    2224     */
    2325    public static final String command = "open_file";
    2426
     27    private final RequestParameter<File> filenameParameter = RequestParameter.mandatory("filename", File::new, "string");
     28
    2529    @Override
    26     public String[] getMandatoryParams() {
    27         return new String[]{"filename"};
     30    public List<RequestParameter<?>> getParameters() {
     31        return Collections.singletonList(filenameParameter);
    2832    }
    2933
    3034    @Override
    3135    public String getUsage() {
    32         return "opens a local file in JOSM";
     36        return "Opens a local file in JOSM";
    3337    }
    3438
    3539    @Override
    3640    public String[] getUsageExamples() {
    37         return new String[] {"/open_file?filename=/tmp/test.osm"};
     41        return new String[]{
     42            "/open_file?filename=/tmp/test.osm"
     43        };
    3844    }
    3945
    4046    @Override
     
    4955            options.add(Options.ALLOW_WEB_RESOURCES);
    5056        }
    5157        GuiHelper.runInEDT(() ->
    52             OpenFileAction.openFiles(Arrays.asList(new File(args.get("filename"))), options.toArray(new Options[0])));
     58            OpenFileAction.openFiles(Collections.singletonList(filenameParameter.read(args)), options.toArray(new Options[0])));
    5359    }
    5460
    5561    @Override
  • src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/RequestHandler.java
    a b  
    66import java.net.URI;
    77import java.net.URISyntaxException;
    88import java.text.MessageFormat;
    9 import java.util.Collections;
     9import java.util.Arrays;
    1010import java.util.HashMap;
    1111import java.util.HashSet;
    1212import java.util.LinkedList;
    1313import java.util.List;
    1414import java.util.Map;
    1515import java.util.Set;
    16 import java.util.function.Function;
    17 import java.util.function.Supplier;
    1816import java.util.regex.Pattern;
    19 
     17import java.util.stream.Collectors;
     18import java.util.stream.Stream;
    2019import javax.swing.JLabel;
    2120import javax.swing.JOptionPane;
    2221
     
    2827import org.openstreetmap.josm.gui.MainApplication;
    2928import org.openstreetmap.josm.io.OsmApiException;
    3029import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
     30import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    3131import org.openstreetmap.josm.spi.preferences.Config;
    3232import org.openstreetmap.josm.tools.Logging;
    3333import org.openstreetmap.josm.tools.Pair;
     
    5353    /** past confirmations */
    5454    protected static final PermissionCache PERMISSIONS = new PermissionCache();
    5555
     56    protected final RequestParameter<String> layerNameParameter = RequestParameter.optional("layer_name");
     57    protected final RequestParameter<Boolean> newLayerParameter = RequestParameter.optional("new_layer", Boolean::parseBoolean, LOAD_IN_NEW_LAYER::get, "boolean");
     58    protected final RequestParameter<Boolean> layerLockedParameter = RequestParameter.optional("layer_locked", Boolean::parseBoolean, () -> false, "boolean");
     59    protected final RequestParameter<DownloadPolicy> downloadPolicyParameter = RequestParameter.optional("download_policy", DownloadPolicy::of, () -> DownloadPolicy.NORMAL, "string");
     60    protected final RequestParameter<UploadPolicy> uploadPolicyParameter = RequestParameter.optional("upload_policy", UploadPolicy::of, () -> UploadPolicy.NORMAL, "string");
     61    protected final RequestParameter<String> addTagsParameter = RequestParameter.optional("addtags");
     62
    5663    /** The GET request arguments */
    5764    protected Map<String, String> args;
    5865
     
    128135     */
    129136    public abstract PermissionPrefWithDefault getPermissionPref();
    130137
     138    /**
     139     * Returns the request parameters. Both used in runtime and for documentation.
     140     * @since xxx
     141     * @return the request parameters
     142     */
     143    public List<RequestParameter<?>> getParameters() {
     144        // Default implementation for backwards compatibility
     145        return Stream.concat(
     146            Arrays.stream(getMandatoryParams()).map(RequestParameter::mandatory),
     147            Arrays.stream(getOptionalParams()).map(RequestParameter::optional)
     148        ).collect(Collectors.toList());
     149    }
     150
    131151    /**
    132152     * Returns the mandatory parameters. Both used to enforce their presence at runtime and for documentation.
     153     * @deprecated implement `getParameters` instead.
    133154     * @return the mandatory parameters
    134155     */
    135     public abstract String[] getMandatoryParams();
     156    @Deprecated
     157    public String[] getMandatoryParams() {
     158        return new String[0];
     159    }
    136160
    137161    /**
    138162     * Returns the optional parameters. Both used to enforce their presence at runtime and for documentation.
     163     * @deprecated implement `getParameters` instead.
    139164     * @return the optional parameters
    140165     */
     166    @Deprecated
    141167    public String[] getOptionalParams() {
    142168        return new String[0];
    143169    }
     
    247273        this.args = getRequestParameter(new URI(this.request));
    248274    }
    249275
    250     protected final String[] splitArg(String arg, Pattern splitter) {
    251         return splitter.split(args != null ? args.get(arg) : "", -1);
    252     }
    253 
    254276    /**
    255277     * Returns the request parameters.
    256278     * @param uri URI as string
     
    271293    }
    272294
    273295    void checkMandatoryParams() throws RequestHandlerBadRequestException {
    274         String[] mandatory = getMandatoryParams();
    275         String[] optional = getOptionalParams();
     296        List<RequestParameter<?>> mandatory = getParameters().stream().filter(RequestParameter::isMandatory).collect(Collectors.toList());
    276297        List<String> missingKeys = new LinkedList<>();
    277298        boolean error = false;
    278         if (mandatory != null && args != null) {
    279             for (String key : mandatory) {
    280                 String value = args.get(key);
    281                 if (Utils.isEmpty(value)) {
     299        if (args != null) {
     300            for (RequestParameter<?> parameter : mandatory) {
     301                String value = args.get(parameter.getName());
     302                if (Utils.isStripEmpty(value)) {
    282303                    error = true;
    283                     Logging.warn('\'' + myCommand + "' remote control request must have '" + key + "' parameter");
    284                     missingKeys.add(key);
     304                    Logging.warn('\'' + myCommand + "' remote control request must have '" + parameter.getName() + "' parameter");
     305                    missingKeys.add(parameter.getName());
    285306                }
    286307            }
    287308        }
    288         Set<String> knownParams = new HashSet<>();
    289         if (mandatory != null)
    290             Collections.addAll(knownParams, mandatory);
    291         if (optional != null)
    292             Collections.addAll(knownParams, optional);
     309        Set<String> knownParams = getParameters().stream().map(RequestParameter::getName).collect(Collectors.toSet());
    293310        if (args != null) {
    294311            for (String par: args.keySet()) {
    295312                if (!knownParams.contains(par)) {
     
    340357        return contentType;
    341358    }
    342359
    343     private <T> T get(String key, Function<String, T> parser, Supplier<T> defaultSupplier) {
    344         String val = args.get(key);
    345         return !Utils.isEmpty(val) ? parser.apply(val) : defaultSupplier.get();
    346     }
    347 
    348     private boolean get(String key) {
    349         return get(key, Boolean::parseBoolean, () -> Boolean.FALSE);
    350     }
    351 
    352     private boolean isLoadInNewLayer() {
    353         return get("new_layer", Boolean::parseBoolean, LOAD_IN_NEW_LAYER::get);
    354     }
    355 
    356360    protected DownloadParams getDownloadParams() {
    357361        DownloadParams result = new DownloadParams();
    358362        if (args != null) {
    359363            result = result
    360                 .withNewLayer(isLoadInNewLayer())
    361                 .withLayerName(args.get("layer_name"))
    362                 .withLocked(get("layer_locked"))
    363                 .withDownloadPolicy(get("download_policy", DownloadPolicy::of, () -> DownloadPolicy.NORMAL))
    364                 .withUploadPolicy(get("upload_policy", UploadPolicy::of, () -> UploadPolicy.NORMAL));
     364                .withNewLayer(newLayerParameter.read(args))
     365                .withLayerName(layerNameParameter.read(args))
     366                .withLocked(layerLockedParameter.read(args))
     367                .withDownloadPolicy(downloadPolicyParameter.read(args))
     368                .withUploadPolicy(uploadPolicyParameter.read(args));
    365369        }
    366370        return result;
    367371    }
     
    501505     */
    502506    public abstract static class RawURLParseRequestHandler extends RequestHandler {
    503507        @Override
    504         protected void parseArgs() throws URISyntaxException {
     508        protected void parseArgs() {
    505509            Map<String, String> args = new HashMap<>();
    506510            if (request.indexOf('?') != -1) {
    507511                String query = request.substring(request.indexOf('?') + 1);
  • src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java b/src/org/openstreetmap/josm/io/remotecontrol/handler/VersionHandler.java
    a b  
    55
    66import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
    77import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
     8import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
     9
     10import java.util.Arrays;
     11import java.util.Collections;
     12import java.util.List;
    813
    914/**
    1015 * Handler for version request.
     
    1621     */
    1722    public static final String command = "version";
    1823
     24    private final RequestParameter<String> jsonpParameter = RequestParameter.optional("jsonp");
     25
    1926    @Override
    2027    protected void handleRequest() throws RequestHandlerErrorException,
    2128            RequestHandlerBadRequestException {
    2229        content = RequestProcessor.PROTOCOLVERSION;
    2330        contentType = "application/json";
    24         if (args.containsKey("jsonp")) {
    25             content = args.get("jsonp") + " && " + args.get("jsonp") + '(' + content + ')';
     31        if (jsonpParameter.isPresent(args)) {
     32            content = jsonpParameter.read(args) + " && " + jsonpParameter.read(args) + '(' + content + ')';
    2633        }
    2734    }
    2835
     
    3744    }
    3845
    3946    @Override
    40     public String[] getMandatoryParams() {
    41         return new String[0];
    42     }
    43 
    44     @Override
    45     public String[] getOptionalParams() {
    46         return new String[]{"jsonp"};
     47    public List<RequestParameter<?>> getParameters() {
     48        return Collections.singletonList(
     49            jsonpParameter
     50        );
    4751    }
    4852
    4953    @Override
     
    5357
    5458    @Override
    5559    public String getUsage() {
    56         return "returns the current protocol version of the installed JOSM RemoteControl";
     60        return "Returns the current protocol version of the installed JOSM RemoteControl";
    5761    }
    5862
    5963    @Override
    6064    public String[] getUsageExamples() {
    61         return new String[] {"/version", "/version?jsonp=test"};
     65        return new String[]{
     66            "/version",
     67            "/version?jsonp=test"
     68        };
    6269    }
    6370}
  • new file src/org/openstreetmap/josm/io/remotecontrol/parameter/RequestParameter.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/parameter/RequestParameter.java b/src/org/openstreetmap/josm/io/remotecontrol/parameter/RequestParameter.java
    new file mode 100644
    - +  
     1package org.openstreetmap.josm.io.remotecontrol.parameter;
     2
     3import org.openstreetmap.josm.tools.Utils;
     4
     5import java.util.Map;
     6import java.util.Objects;
     7import java.util.function.Function;
     8import java.util.function.Supplier;
     9
     10/**
     11 * Logic for parsing a mandatory or optional request parameter with a name, a default value and an OpenAPI type.
     12 * @since xxx
     13 */
     14public class RequestParameter<T> {
     15
     16    private final String name;
     17    private final Function<String, T> parser;
     18    private final boolean mandatory;
     19    private final Supplier<T> defaultValue;
     20    private final String openapiType;
     21
     22    private RequestParameter(String name, Function<String, T> parser, boolean mandatory, Supplier<T> defaultValue, String openapiType) {
     23        this.name = Objects.requireNonNull(name, "name");
     24        this.parser = Objects.requireNonNull(parser, "parser");
     25        this.mandatory = mandatory;
     26        this.defaultValue = defaultValue;
     27        this.openapiType = openapiType;
     28    }
     29
     30    public static <T> RequestParameter<T> mandatory(String name, Function<String, T> parser, String openapiType) {
     31        return new RequestParameter<>(name, parser, true, () -> null, openapiType);
     32    }
     33
     34    public static RequestParameter<String> mandatory(String name) {
     35        return new RequestParameter<>(name, Function.identity(), true, () -> null, "string");
     36    }
     37
     38    public static <T> RequestParameter<T> optional(String name, Function<String, T> parser, String openapiType) {
     39        return new RequestParameter<>(name, parser, false, () -> null, openapiType);
     40    }
     41
     42    public static <T> RequestParameter<T> optional(String name, Function<String, T> parser, Supplier<T> defaultValue, String openapiType) {
     43        return new RequestParameter<>(name, parser, false, defaultValue, openapiType);
     44    }
     45
     46    public static RequestParameter<String> optional(String name) {
     47        return new RequestParameter<>(name, Function.identity(), false, () -> null, "string");
     48    }
     49
     50    public static RequestParameter<String> optional(String name, Supplier<String> defaultValue) {
     51        return new RequestParameter<>(name, Function.identity(), false, defaultValue, "string");
     52    }
     53
     54    public String getName() {
     55        return name;
     56    }
     57
     58    public boolean isMandatory() {
     59        return mandatory;
     60    }
     61
     62    public boolean isOptional() {
     63        return !mandatory;
     64    }
     65
     66    /**
     67     * Is the parameter present in the request
     68     * @param arguments the request parameters
     69     */
     70    public boolean isPresent(Map<String, String> arguments) {
     71        return arguments.containsKey(name);
     72    }
     73
     74    /**
     75     * Read the parameter from the request
     76     * @param arguments the request parameters
     77     * @return The parameter, or the default value if the parameter is missing.
     78     * @throws IllegalArgumentException when a mandatory parameter is missing.
     79     */
     80    public T read(Map<String, String> arguments) {
     81        String value = arguments.get(name);
     82        if (Utils.isStripEmpty(value)) {
     83            if (mandatory) {
     84                throw new IllegalArgumentException("Empty value supplied for mandatory parameter");
     85            }
     86            return defaultValue.get();
     87        }
     88
     89        return parser.apply(value);
     90    }
     91
     92    /**
     93     * For use in the OpenAPI documentation of the JOSM API.
     94     * @see <a href="https://swagger.io/docs/specification/data-models/data-types/">OpenAPI data types</a>
     95     */
     96    public String getOpenapiType() {
     97        return openapiType;
     98    }
     99}
  • src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java b/src/org/openstreetmap/josm/io/remotecontrol/RemoteControlHttpServer.java
    a b  
    88import java.net.Socket;
    99import java.net.SocketException;
    1010
    11 import org.openstreetmap.josm.spi.preferences.Config;
     11import org.openstreetmap.josm.data.preferences.IntegerProperty;
    1212import org.openstreetmap.josm.tools.Logging;
    1313
    1414/**
     
    1919 */
    2020public class RemoteControlHttpServer extends Thread {
    2121
     22    /**
     23     * preference to define remote control port
     24     */
     25    public static final IntegerProperty PORT = new IntegerProperty("remote.control.port", 8111);
     26
    2227    /** The server socket */
    2328    private final ServerSocket server;
    2429
     
    3237     */
    3338    public static void restartRemoteControlHttpServer() {
    3439        stopRemoteControlHttpServer();
    35         int port = Config.getPref().getInt("remote.control.port", 8111);
     40        int port = PORT.get();
    3641        try {
    3742            instance4 = new RemoteControlHttpServer(port, false);
    3843            instance4.start();
  • src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java b/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java
    a b  
    1313import java.util.Collection;
    1414import java.util.Date;
    1515import java.util.HashMap;
     16import java.util.List;
    1617import java.util.Locale;
    1718import java.util.Map;
    1819import java.util.Map.Entry;
     
    2223import java.util.TreeMap;
    2324import java.util.regex.Matcher;
    2425import java.util.regex.Pattern;
     26import java.util.stream.Collectors;
    2527import java.util.stream.Stream;
    2628
    2729import javax.json.Json;
     
    4446import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerForbiddenException;
    4547import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler.RequestHandlerOsmApiException;
    4648import org.openstreetmap.josm.io.remotecontrol.handler.VersionHandler;
     49import org.openstreetmap.josm.io.remotecontrol.parameter.RequestParameter;
    4750import org.openstreetmap.josm.tools.Logging;
    4851import org.openstreetmap.josm.tools.Utils;
    4952
     
    450453        StringBuilder usage = new StringBuilder(1024);
    451454        for (Entry<String, Class<? extends RequestHandler>> handler : handlers.entrySet()) {
    452455            RequestHandler sample = handler.getValue().getConstructor().newInstance();
    453             String[] mandatory = sample.getMandatoryParams();
    454             String[] optional = sample.getOptionalParams();
     456            List<String> mandatory = sample.getParameters().stream().filter(RequestParameter::isMandatory).map(RequestParameter::getName).sorted().collect(Collectors.toList());
     457            List<String> optional = sample.getParameters().stream().filter(RequestParameter::isOptional).map(RequestParameter::getName).sorted().collect(Collectors.toList());
    455458            String[] examples = sample.getUsageExamples(handler.getKey().substring(1));
    456459            usage.append("<li>")
    457460                 .append(handler.getKey());
    458461            if (!Utils.isEmpty(sample.getUsage())) {
    459462                usage.append(" &mdash; <i>").append(sample.getUsage()).append("</i>");
    460463            }
    461             if (mandatory != null && mandatory.length > 0) {
     464            if (!mandatory.isEmpty()) {
    462465                usage.append("<br/>mandatory parameters: ").append(String.join(", ", mandatory));
    463466            }
    464             if (optional != null && optional.length > 0) {
     467            if (!optional.isEmpty()) {
    465468                usage.append("<br/>optional parameters: ").append(String.join(", ", optional));
    466469            }
    467470            if (examples != null && examples.length > 0) {
    468471                usage.append("<br/>examples: ");
    469472                for (String ex: examples) {
    470                     usage.append("<br/> <a href=\"http://localhost:8111").append(ex).append("\">").append(ex).append("</a>");
     473                    usage.append("<br/> <a href=\"http://localhost:" + RemoteControlHttpServer.PORT.get()).append(ex).append("\">").append(ex).append("</a>");
    471474                }
    472475            }
    473476            usage.append("</li>");
  • test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java b/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java
    a b  
    4141    @BeforeEach
    4242    public void setUp() throws GeneralSecurityException {
    4343        RemoteControl.start();
    44         httpBase = "http://127.0.0.1:"+Config.getPref().getInt("remote.control.port", 8111);
     44        httpBase = String.format("http://127.0.0.1:%s", RemoteControlHttpServer.PORT.get());
    4545    }
    4646
    4747    /**