001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.io.IOException;
005import java.io.Reader;
006
007import javax.script.Invocable;
008import javax.script.ScriptEngine;
009import javax.script.ScriptEngineManager;
010import javax.script.ScriptException;
011
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.io.CachedFile;
014
015/**
016 * Uses <a href="https://github.com/tyrasd/overpass-wizard/">Overpass Turbo query wizard</a> code (MIT Licensed)
017 * to build an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
018 *
019 * Requires a JavaScript {@link ScriptEngine}.
020 * @since 8744
021 */
022public final class OverpassTurboQueryWizard {
023
024    private static OverpassTurboQueryWizard instance;
025    private final ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
026
027    /**
028     * Replies the unique instance of this class.
029     *
030     * @return the unique instance of this class
031     */
032    public static synchronized OverpassTurboQueryWizard getInstance() {
033        if (instance == null) {
034            instance = new OverpassTurboQueryWizard();
035        }
036        return instance;
037    }
038
039    private OverpassTurboQueryWizard() {
040
041        try (final CachedFile file = new CachedFile("resource://data/overpass-wizard.js");
042             final Reader reader = file.getContentReader()) {
043            engine.eval("var console = {error: " + Main.class.getCanonicalName() + ".warn};");
044            engine.eval("var global = {};");
045            engine.eval(reader);
046            engine.eval("var overpassWizard = function(query) {" +
047                    "  return global.overpassWizard(query, {" +
048                    "    comment: false," +
049                    "    outputFormat: 'xml'," +
050                    "    outputMode: 'recursive_meta'" +
051                    "  });" +
052                    "}");
053        } catch (ScriptException | IOException ex) {
054            throw new RuntimeException("Failed to initialize OverpassTurboQueryWizard", ex);
055        }
056    }
057
058    /**
059     * Builds an Overpass QL from a {@link org.openstreetmap.josm.actions.search.SearchAction} like query.
060     * @param search the {@link org.openstreetmap.josm.actions.search.SearchAction} like query
061     * @return an Overpass QL query
062     * @throws UncheckedParseException when the parsing fails
063     */
064    public String constructQuery(String search) throws UncheckedParseException {
065        try {
066            final Object result = ((Invocable) engine).invokeFunction("overpassWizard", search);
067            if (Boolean.FALSE.equals(result)) {
068                throw new UncheckedParseException();
069            }
070            String query = (String) result;
071            query = query.replace("[bbox:{{bbox}}]", "");
072            return query;
073        } catch (NoSuchMethodException e) {
074            throw new IllegalStateException();
075        } catch (ScriptException e) {
076            throw new RuntimeException("Failed to execute OverpassTurboQueryWizard", e);
077        }
078    }
079
080}