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}