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