source: josm/trunk/src/org/openstreetmap/josm/gui/io/CustomConfigurator.java @ 12841

Last change on this file since 12841 was 12841, checked in by bastiK, 3 months ago

see #15229 - fix deprecations caused by [12840]

  • Property svn:eol-style set to native
File size: 31.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedInputStream;
7import java.io.ByteArrayInputStream;
8import java.io.CharArrayReader;
9import java.io.CharArrayWriter;
10import java.io.File;
11import java.io.FileInputStream;
12import java.io.IOException;
13import java.io.InputStream;
14import java.nio.charset.StandardCharsets;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.HashMap;
19import java.util.HashSet;
20import java.util.List;
21import java.util.Locale;
22import java.util.Map;
23import java.util.Set;
24import java.util.regex.Matcher;
25import java.util.regex.Pattern;
26
27import javax.script.ScriptEngine;
28import javax.script.ScriptEngineManager;
29import javax.script.ScriptException;
30import javax.swing.JOptionPane;
31import javax.swing.SwingUtilities;
32import javax.xml.parsers.DocumentBuilder;
33import javax.xml.parsers.ParserConfigurationException;
34import javax.xml.stream.XMLStreamException;
35import javax.xml.transform.OutputKeys;
36import javax.xml.transform.Transformer;
37import javax.xml.transform.TransformerException;
38import javax.xml.transform.TransformerFactory;
39import javax.xml.transform.TransformerFactoryConfigurationError;
40import javax.xml.transform.dom.DOMSource;
41import javax.xml.transform.stream.StreamResult;
42
43import org.openstreetmap.josm.Main;
44import org.openstreetmap.josm.data.Preferences;
45import org.openstreetmap.josm.data.PreferencesUtils;
46import org.openstreetmap.josm.data.Version;
47import org.openstreetmap.josm.data.preferences.Setting;
48import org.openstreetmap.josm.gui.MainApplication;
49import org.openstreetmap.josm.plugins.PluginDownloadTask;
50import org.openstreetmap.josm.plugins.PluginInformation;
51import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
52import org.openstreetmap.josm.tools.LanguageInfo;
53import org.openstreetmap.josm.tools.Logging;
54import org.openstreetmap.josm.tools.Utils;
55import org.w3c.dom.DOMException;
56import org.w3c.dom.Document;
57import org.w3c.dom.Element;
58import org.w3c.dom.Node;
59import org.w3c.dom.NodeList;
60import org.xml.sax.SAXException;
61
62/**
63 * Class to process configuration changes stored in XML
64 * can be used to modify preferences, store/delete files in .josm folders etc
65 */
66public final class CustomConfigurator {
67
68    private CustomConfigurator() {
69        // Hide default constructor for utils classes
70    }
71
72    /**
73     * Log a formatted message.
74     * @param fmt format
75     * @param vars arguments
76     * @see String#format
77     * @deprecated to be removed end of 2017. Use {@link PreferencesUtils#log(String, Object...)} instead
78     */
79    @Deprecated
80    public static void log(String fmt, Object... vars) {
81        PreferencesUtils.log(fmt, vars);
82    }
83
84    /**
85     * Log a message.
86     * @param s message to log
87     * @deprecated to be removed end of 2017. Use {@link PreferencesUtils#log(String)} instead
88     */
89    @Deprecated
90    public static void log(String s) {
91        PreferencesUtils.log(s);
92    }
93
94    /**
95     * Log an exception.
96     * @param e exception to log
97     * @param s message prefix
98     * @since 10469
99     * @deprecated to be removed end of 2017. Use {@link PreferencesUtils#log(Exception, String)} instead
100     */
101    @Deprecated
102    public static void log(Exception e, String s) {
103        PreferencesUtils.log(e, s);
104    }
105
106    /**
107     * Returns the log.
108     * @return the log
109     * @deprecated to be removed end of 2017. Use {@link PreferencesUtils#getLog()} instead
110     */
111    @Deprecated
112    public static String getLog() {
113        return PreferencesUtils.getLog();
114    }
115
116    /**
117     * Resets the log.
118     * @deprecated to be removed end of 2017. Use {@link PreferencesUtils#resetLog()} instead
119     */
120    @Deprecated
121    public static void resetLog() {
122        PreferencesUtils.resetLog();
123    }
124
125    /**
126     * Read configuration script from XML file, modifying main preferences
127     * @param dir - directory
128     * @param fileName - XML file name
129     */
130    public static void readXML(String dir, String fileName) {
131        readXML(new File(dir, fileName));
132    }
133
134    /**
135     * Read configuration script from XML file, modifying given preferences object
136     * @param file - file to open for reading XML
137     * @param prefs - arbitrary Preferences object to modify by script
138     */
139    public static void readXML(final File file, final Preferences prefs) {
140        synchronized (CustomConfigurator.class) {
141            busy = true;
142        }
143        new XMLCommandProcessor(prefs).openAndReadXML(file);
144        synchronized (CustomConfigurator.class) {
145            CustomConfigurator.class.notifyAll();
146            busy = false;
147        }
148    }
149
150    /**
151     * Read configuration script from XML file, modifying main preferences
152     * @param file - file to open for reading XML
153     */
154    public static void readXML(File file) {
155        readXML(file, Main.pref);
156    }
157
158    /**
159     * Downloads file to one of JOSM standard folders
160     * @param address - URL to download
161     * @param path - file path relative to base where to put downloaded file
162     * @param base - only "prefs", "cache" and "plugins" allowed for standard folders
163     */
164    public static void downloadFile(String address, String path, String base) {
165        processDownloadOperation(address, path, getDirectoryByAbbr(base), true, false);
166    }
167
168    /**
169     * Downloads file to one of JOSM standard folders and unpack it as ZIP/JAR file
170     * @param address - URL to download
171     * @param path - file path relative to base where to put downloaded file
172     * @param base - only "prefs", "cache" and "plugins" allowed for standard folders
173     */
174    public static void downloadAndUnpackFile(String address, String path, String base) {
175        processDownloadOperation(address, path, getDirectoryByAbbr(base), true, true);
176    }
177
178    /**
179     * Downloads file to arbitrary folder
180     * @param address - URL to download
181     * @param path - file path relative to parentDir where to put downloaded file
182     * @param parentDir - folder where to put file
183     * @param mkdir - if true, non-existing directories will be created
184     * @param unzip - if true file wil be unzipped and deleted after download
185     */
186    public static void processDownloadOperation(String address, String path, String parentDir, boolean mkdir, boolean unzip) {
187        String dir = parentDir;
188        if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
189            return; // some basic protection
190        }
191        File fOut = new File(dir, path);
192        DownloadFileTask downloadFileTask = new DownloadFileTask(Main.parent, address, fOut, mkdir, unzip);
193
194        MainApplication.worker.submit(downloadFileTask);
195        PreferencesUtils.log("Info: downloading file from %s to %s in background ", parentDir, fOut.getAbsolutePath());
196        if (unzip) PreferencesUtils.log("and unpacking it"); else PreferencesUtils.log("");
197
198    }
199
200    /**
201     * Simple function to show messageBox, may be used from JS API and from other code
202     * @param type - 'i','w','e','q','p' for Information, Warning, Error, Question, Message
203     * @param text - message to display, HTML allowed
204     */
205    public static void messageBox(String type, String text) {
206        char c = (type == null || type.isEmpty() ? "plain" : type).charAt(0);
207        switch (c) {
208            case 'i': JOptionPane.showMessageDialog(Main.parent, text, tr("Information"), JOptionPane.INFORMATION_MESSAGE); break;
209            case 'w': JOptionPane.showMessageDialog(Main.parent, text, tr("Warning"), JOptionPane.WARNING_MESSAGE); break;
210            case 'e': JOptionPane.showMessageDialog(Main.parent, text, tr("Error"), JOptionPane.ERROR_MESSAGE); break;
211            case 'q': JOptionPane.showMessageDialog(Main.parent, text, tr("Question"), JOptionPane.QUESTION_MESSAGE); break;
212            case 'p': JOptionPane.showMessageDialog(Main.parent, text, tr("Message"), JOptionPane.PLAIN_MESSAGE); break;
213            default: Logging.warn("Unsupported messageBox type: " + c);
214        }
215    }
216
217    /**
218     * Simple function for choose window, may be used from JS API and from other code
219     * @param text - message to show, HTML allowed
220     * @param opts -
221     * @return number of pressed button, -1 if cancelled
222     */
223    public static int askForOption(String text, String opts) {
224        if (!opts.isEmpty()) {
225            return JOptionPane.showOptionDialog(Main.parent, text, "Question",
226                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, opts.split(";"), 0);
227        } else {
228            return JOptionPane.showOptionDialog(Main.parent, text, "Question",
229                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, 2);
230        }
231    }
232
233    public static String askForText(String text) {
234        String s = JOptionPane.showInputDialog(Main.parent, text, tr("Enter text"), JOptionPane.QUESTION_MESSAGE);
235        return s != null ? s.trim() : null;
236    }
237
238    /**
239     * This function exports part of user preferences to specified file.
240     * Default values are not saved.
241     * @param filename - where to export
242     * @param append - if true, resulting file cause appending to exuisting preferences
243     * @param keys - which preferences keys you need to export ("imagery.entries", for example)
244     */
245    public static void exportPreferencesKeysToFile(String filename, boolean append, String... keys) {
246        Set<String> keySet = new HashSet<>();
247        Collections.addAll(keySet, keys);
248        exportPreferencesKeysToFile(filename, append, keySet);
249    }
250
251    /**
252     * This function exports part of user preferences to specified file.
253     * Default values are not saved.
254     * Preference keys matching specified pattern are saved
255     * @param fileName - where to export
256     * @param append - if true, resulting file cause appending to exuisting preferences
257     * @param pattern - Regexp pattern forh preferences keys you need to export (".*imagery.*", for example)
258     */
259    public static void exportPreferencesKeysByPatternToFile(String fileName, boolean append, String pattern) {
260        List<String> keySet = new ArrayList<>();
261        Map<String, Setting<?>> allSettings = Main.pref.getAllSettings();
262        for (String key: allSettings.keySet()) {
263            if (key.matches(pattern))
264                keySet.add(key);
265        }
266        exportPreferencesKeysToFile(fileName, append, keySet);
267    }
268
269    /**
270     * Export specified preferences keys to configuration file
271     * @param filename - name of file
272     * @param append - will the preferences be appended to existing ones when file is imported later.
273     * Elsewhere preferences from file will replace existing keys.
274     * @param keys - collection of preferences key names to save
275     */
276    public static void exportPreferencesKeysToFile(String filename, boolean append, Collection<String> keys) {
277        Element root = null;
278        Document document = null;
279        Document exportDocument = null;
280
281        try {
282            String toXML = Main.pref.toXML(true);
283            DocumentBuilder builder = Utils.newSafeDOMBuilder();
284            document = builder.parse(new ByteArrayInputStream(toXML.getBytes(StandardCharsets.UTF_8)));
285            exportDocument = builder.newDocument();
286            root = document.getDocumentElement();
287        } catch (SAXException | IOException | ParserConfigurationException ex) {
288            Logging.log(Logging.LEVEL_WARN, "Error getting preferences to save:", ex);
289        }
290        if (root == null || exportDocument == null)
291            return;
292        try {
293            Element newRoot = exportDocument.createElement("config");
294            exportDocument.appendChild(newRoot);
295
296            Element prefElem = exportDocument.createElement("preferences");
297            prefElem.setAttribute("operation", append ? "append" : "replace");
298            newRoot.appendChild(prefElem);
299
300            NodeList childNodes = root.getChildNodes();
301            int n = childNodes.getLength();
302            for (int i = 0; i < n; i++) {
303                Node item = childNodes.item(i);
304                if (item.getNodeType() == Node.ELEMENT_NODE) {
305                    String currentKey = ((Element) item).getAttribute("key");
306                    if (keys.contains(currentKey)) {
307                        Node imported = exportDocument.importNode(item, true);
308                        prefElem.appendChild(imported);
309                    }
310                }
311            }
312            File f = new File(filename);
313            Transformer ts = TransformerFactory.newInstance().newTransformer();
314            ts.setOutputProperty(OutputKeys.INDENT, "yes");
315            ts.transform(new DOMSource(exportDocument), new StreamResult(f.toURI().getPath()));
316        } catch (DOMException | TransformerFactoryConfigurationError | TransformerException ex) {
317            Logging.warn("Error saving preferences part:");
318            Logging.error(ex);
319        }
320    }
321
322    public static void deleteFile(String path, String base) {
323        String dir = getDirectoryByAbbr(base);
324        if (dir == null) {
325            PreferencesUtils.log("Error: Can not find base, use base=cache, base=prefs or base=plugins attribute.");
326            return;
327        }
328        PreferencesUtils.log("Delete file: %s\n", path);
329        if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
330            return; // some basic protection
331        }
332        File fOut = new File(dir, path);
333        if (fOut.exists()) {
334            deleteFileOrDirectory(fOut);
335        }
336    }
337
338    public static void deleteFileOrDirectory(File f) {
339        if (f.isDirectory()) {
340            File[] files = f.listFiles();
341            if (files != null) {
342                for (File f1: files) {
343                    deleteFileOrDirectory(f1);
344                }
345            }
346        }
347        if (!Utils.deleteFile(f)) {
348            PreferencesUtils.log("Warning: Can not delete file "+f.getPath());
349        }
350    }
351
352    private static boolean busy;
353
354    public static void pluginOperation(String install, String uninstall, String delete) {
355        final List<String> installList = new ArrayList<>();
356        final List<String> removeList = new ArrayList<>();
357        final List<String> deleteList = new ArrayList<>();
358        Collections.addAll(installList, install.toLowerCase(Locale.ENGLISH).split(";"));
359        Collections.addAll(removeList, uninstall.toLowerCase(Locale.ENGLISH).split(";"));
360        Collections.addAll(deleteList, delete.toLowerCase(Locale.ENGLISH).split(";"));
361        installList.remove("");
362        removeList.remove("");
363        deleteList.remove("");
364
365        if (!installList.isEmpty()) {
366            PreferencesUtils.log("Plugins install: "+installList);
367        }
368        if (!removeList.isEmpty()) {
369            PreferencesUtils.log("Plugins turn off: "+removeList);
370        }
371        if (!deleteList.isEmpty()) {
372            PreferencesUtils.log("Plugins delete: "+deleteList);
373        }
374
375        final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask();
376        Runnable r = () -> {
377            if (task.isCanceled()) return;
378            synchronized (CustomConfigurator.class) {
379                try { // proceed only after all other tasks were finished
380                    while (busy) CustomConfigurator.class.wait();
381                } catch (InterruptedException ex) {
382                    Logging.log(Logging.LEVEL_WARN, "InterruptedException while reading local plugin information", ex);
383                    Thread.currentThread().interrupt();
384                }
385
386                SwingUtilities.invokeLater(() -> {
387                    List<PluginInformation> availablePlugins = task.getAvailablePlugins();
388                    List<PluginInformation> toInstallPlugins = new ArrayList<>();
389                    List<PluginInformation> toRemovePlugins = new ArrayList<>();
390                    List<PluginInformation> toDeletePlugins = new ArrayList<>();
391                    for (PluginInformation pi1: availablePlugins) {
392                        String name = pi1.name.toLowerCase(Locale.ENGLISH);
393                        if (installList.contains(name)) toInstallPlugins.add(pi1);
394                        if (removeList.contains(name)) toRemovePlugins.add(pi1);
395                        if (deleteList.contains(name)) toDeletePlugins.add(pi1);
396                    }
397                    if (!installList.isEmpty()) {
398                        PluginDownloadTask pluginDownloadTask =
399                                new PluginDownloadTask(Main.parent, toInstallPlugins, tr("Installing plugins"));
400                        MainApplication.worker.submit(pluginDownloadTask);
401                    }
402                    List<String> pls = new ArrayList<>(Main.pref.getList("plugins"));
403                    for (PluginInformation pi2: toInstallPlugins) {
404                        if (!pls.contains(pi2.name)) {
405                            pls.add(pi2.name);
406                        }
407                    }
408                    for (PluginInformation pi3: toRemovePlugins) {
409                        pls.remove(pi3.name);
410                    }
411                    for (PluginInformation pi4: toDeletePlugins) {
412                        pls.remove(pi4.name);
413                        new File(Main.pref.getPluginsDirectory(), pi4.name+".jar").deleteOnExit();
414                    }
415                    Main.pref.putList("plugins", pls);
416                });
417            }
418        };
419        MainApplication.worker.submit(task);
420        MainApplication.worker.submit(r);
421    }
422
423    private static String getDirectoryByAbbr(String base) {
424        String dir;
425        if ("prefs".equals(base) || base.isEmpty()) {
426            dir = Main.pref.getPreferencesDirectory().getAbsolutePath();
427        } else if ("cache".equals(base)) {
428            dir = Main.pref.getCacheDirectory().getAbsolutePath();
429        } else if ("plugins".equals(base)) {
430            dir = Main.pref.getPluginsDirectory().getAbsolutePath();
431        } else {
432            dir = null;
433        }
434        return dir;
435    }
436
437    public static class XMLCommandProcessor {
438
439        private Preferences mainPrefs;
440        private final Map<String, Element> tasksMap = new HashMap<>();
441
442        private boolean lastV; // last If condition result
443
444        private ScriptEngine engine;
445
446        public void openAndReadXML(File file) {
447            PreferencesUtils.log("-- Reading custom preferences from " + file.getAbsolutePath() + " --");
448            try {
449                String fileDir = file.getParentFile().getAbsolutePath();
450                if (fileDir != null) engine.eval("scriptDir='"+normalizeDirName(fileDir) +"';");
451                try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
452                    openAndReadXML(is);
453                }
454            } catch (ScriptException | IOException | SecurityException ex) {
455                PreferencesUtils.log(ex, "Error reading custom preferences:");
456            }
457        }
458
459        public void openAndReadXML(InputStream is) {
460            try {
461                Document document = Utils.parseSafeDOM(is);
462                synchronized (CustomConfigurator.class) {
463                    processXML(document);
464                }
465            } catch (SAXException | IOException | ParserConfigurationException ex) {
466                PreferencesUtils.log(ex, "Error reading custom preferences:");
467            }
468            PreferencesUtils.log("-- Reading complete --");
469        }
470
471        public XMLCommandProcessor(Preferences mainPrefs) {
472            try {
473                this.mainPrefs = mainPrefs;
474                PreferencesUtils.resetLog();
475                engine = new ScriptEngineManager().getEngineByName("JavaScript");
476                engine.eval("API={}; API.pref={}; API.fragments={};");
477
478                engine.eval("homeDir='"+normalizeDirName(Main.pref.getPreferencesDirectory().getAbsolutePath()) +"';");
479                engine.eval("josmVersion="+Version.getInstance().getVersion()+';');
480                String className = CustomConfigurator.class.getName();
481                engine.eval("API.messageBox="+className+".messageBox");
482                engine.eval("API.askText=function(text) { return String("+className+".askForText(text));}");
483                engine.eval("API.askOption="+className+".askForOption");
484                engine.eval("API.downloadFile="+className+".downloadFile");
485                engine.eval("API.downloadAndUnpackFile="+className+".downloadAndUnpackFile");
486                engine.eval("API.deleteFile="+className+".deleteFile");
487                engine.eval("API.plugin ="+className+".pluginOperation");
488                engine.eval("API.pluginInstall = function(names) { "+className+".pluginOperation(names,'','');}");
489                engine.eval("API.pluginUninstall = function(names) { "+className+".pluginOperation('',names,'');}");
490                engine.eval("API.pluginDelete = function(names) { "+className+".pluginOperation('','',names);}");
491            } catch (ScriptException ex) {
492                PreferencesUtils.log("Error: initializing script engine: "+ex.getMessage());
493                Logging.error(ex);
494            }
495        }
496
497        private void processXML(Document document) {
498            processXmlFragment(document.getDocumentElement());
499        }
500
501        private void processXmlFragment(Element root) {
502            NodeList childNodes = root.getChildNodes();
503            int nops = childNodes.getLength();
504            for (int i = 0; i < nops; i++) {
505                Node item = childNodes.item(i);
506                if (item.getNodeType() != Node.ELEMENT_NODE) continue;
507                String elementName = item.getNodeName();
508                Element elem = (Element) item;
509
510                switch(elementName) {
511                case "var":
512                    setVar(elem.getAttribute("name"), evalVars(elem.getAttribute("value")));
513                    break;
514                case "task":
515                    tasksMap.put(elem.getAttribute("name"), elem);
516                    break;
517                case "runtask":
518                    if (processRunTaskElement(elem)) return;
519                    break;
520                case "ask":
521                    processAskElement(elem);
522                    break;
523                case "if":
524                    processIfElement(elem);
525                    break;
526                case "else":
527                    processElseElement(elem);
528                    break;
529                case "break":
530                    return;
531                case "plugin":
532                    processPluginInstallElement(elem);
533                    break;
534                case "messagebox":
535                    processMsgBoxElement(elem);
536                    break;
537                case "preferences":
538                    processPreferencesElement(elem);
539                    break;
540                case "download":
541                    processDownloadElement(elem);
542                    break;
543                case "delete":
544                    processDeleteElement(elem);
545                    break;
546                case "script":
547                    processScriptElement(elem);
548                    break;
549                default:
550                    PreferencesUtils.log("Error: Unknown element " + elementName);
551                }
552            }
553        }
554
555        private void processPreferencesElement(Element item) {
556            String oper = evalVars(item.getAttribute("operation"));
557            String id = evalVars(item.getAttribute("id"));
558
559            if ("delete-keys".equals(oper)) {
560                String pattern = evalVars(item.getAttribute("pattern"));
561                String key = evalVars(item.getAttribute("key"));
562                PreferencesUtils.deletePreferenceKey(key, mainPrefs);
563                PreferencesUtils.deletePreferenceKeyByPattern(pattern, mainPrefs);
564                return;
565            }
566
567            Preferences tmpPref = readPreferencesFromDOMElement(item);
568            PreferencesUtils.showPrefs(tmpPref);
569
570            if (!id.isEmpty()) {
571                try {
572                    String fragmentVar = "API.fragments['"+id+"']";
573                    engine.eval(fragmentVar+"={};");
574                    PreferencesUtils.loadPrefsToJS(engine, tmpPref, fragmentVar, false);
575                    // we store this fragment as API.fragments['id']
576                } catch (ScriptException ex) {
577                    PreferencesUtils.log(ex, "Error: can not load preferences fragment:");
578                }
579            }
580
581            if ("replace".equals(oper)) {
582                PreferencesUtils.log("Preferences replace: %d keys: %s\n",
583                   tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString());
584                PreferencesUtils.replacePreferences(tmpPref, mainPrefs);
585            } else if ("append".equals(oper)) {
586                PreferencesUtils.log("Preferences append: %d keys: %s\n",
587                   tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString());
588                PreferencesUtils.appendPreferences(tmpPref, mainPrefs);
589            } else if ("delete-values".equals(oper)) {
590                PreferencesUtils.deletePreferenceValues(tmpPref, mainPrefs);
591            }
592        }
593
594         private void processDeleteElement(Element item) {
595            String path = evalVars(item.getAttribute("path"));
596            String base = evalVars(item.getAttribute("base"));
597            deleteFile(path, base);
598        }
599
600        private void processDownloadElement(Element item) {
601            String base = evalVars(item.getAttribute("base"));
602            String dir = getDirectoryByAbbr(base);
603            if (dir == null) {
604                PreferencesUtils.log("Error: Can not find directory to place file, use base=cache, base=prefs or base=plugins attribute.");
605                return;
606            }
607
608            String path = evalVars(item.getAttribute("path"));
609            if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
610                return; // some basic protection
611            }
612
613            String address = evalVars(item.getAttribute("url"));
614            if (address.isEmpty() || path.isEmpty()) {
615                PreferencesUtils.log("Error: Please specify url=\"where to get file\" and path=\"where to place it\"");
616                return;
617            }
618
619            String unzip = evalVars(item.getAttribute("unzip"));
620            String mkdir = evalVars(item.getAttribute("mkdir"));
621            processDownloadOperation(address, path, dir, "true".equals(mkdir), "true".equals(unzip));
622        }
623
624        private static void processPluginInstallElement(Element elem) {
625            String install = elem.getAttribute("install");
626            String uninstall = elem.getAttribute("remove");
627            String delete = elem.getAttribute("delete");
628            pluginOperation(install, uninstall, delete);
629        }
630
631        private void processMsgBoxElement(Element elem) {
632            String text = evalVars(elem.getAttribute("text"));
633            String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text"));
634            if (!locText.isEmpty()) text = locText;
635
636            String type = evalVars(elem.getAttribute("type"));
637            messageBox(type, text);
638        }
639
640        private void processAskElement(Element elem) {
641            String text = evalVars(elem.getAttribute("text"));
642            String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text"));
643            if (!locText.isEmpty()) text = locText;
644            String var = elem.getAttribute("var");
645            if (var.isEmpty()) var = "result";
646
647            String input = evalVars(elem.getAttribute("input"));
648            if ("true".equals(input)) {
649                setVar(var, askForText(text));
650            } else {
651                String opts = evalVars(elem.getAttribute("options"));
652                String locOpts = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".options"));
653                if (!locOpts.isEmpty()) opts = locOpts;
654                setVar(var, String.valueOf(askForOption(text, opts)));
655            }
656        }
657
658        public void setVar(String name, String value) {
659            try {
660                engine.eval(name+"='"+value+"';");
661            } catch (ScriptException ex) {
662                PreferencesUtils.log(ex, String.format("Error: Can not assign variable: %s=%s :", name, value));
663            }
664        }
665
666        private void processIfElement(Element elem) {
667            String realValue = evalVars(elem.getAttribute("test"));
668            boolean v = false;
669            if ("true".equals(realValue) || "false".equals(realValue)) {
670                processXmlFragment(elem);
671                v = true;
672            } else {
673                PreferencesUtils.log("Error: Illegal test expression in if: %s=%s\n", elem.getAttribute("test"), realValue);
674            }
675
676            lastV = v;
677        }
678
679        private void processElseElement(Element elem) {
680            if (!lastV) {
681                processXmlFragment(elem);
682            }
683        }
684
685        private boolean processRunTaskElement(Element elem) {
686            String taskName = elem.getAttribute("name");
687            Element task = tasksMap.get(taskName);
688            if (task != null) {
689                PreferencesUtils.log("EXECUTING TASK "+taskName);
690                processXmlFragment(task); // process task recursively
691            } else {
692                PreferencesUtils.log("Error: Can not execute task "+taskName);
693                return true;
694            }
695            return false;
696        }
697
698        private void processScriptElement(Element elem) {
699            String js = elem.getChildNodes().item(0).getTextContent();
700            PreferencesUtils.log("Processing script...");
701            try {
702                PreferencesUtils.modifyPreferencesByScript(engine, mainPrefs, js);
703            } catch (ScriptException ex) {
704                messageBox("e", ex.getMessage());
705                PreferencesUtils.log(ex, "JS error:");
706            }
707            PreferencesUtils.log("Script finished");
708        }
709
710        /**
711         * substitute ${expression} = expression evaluated by JavaScript
712         * @param s string
713         * @return evaluation result
714         */
715        private String evalVars(String s) {
716            Matcher mr = Pattern.compile("\\$\\{([^\\}]*)\\}").matcher(s);
717            StringBuffer sb = new StringBuffer();
718            while (mr.find()) {
719                try {
720                    String result = engine.eval(mr.group(1)).toString();
721                    mr.appendReplacement(sb, result);
722                } catch (ScriptException ex) {
723                    PreferencesUtils.log(ex, String.format("Error: Can not evaluate expression %s :", mr.group(1)));
724                }
725            }
726            mr.appendTail(sb);
727            return sb.toString();
728        }
729
730        private Preferences readPreferencesFromDOMElement(Element item) {
731            Preferences tmpPref = new Preferences();
732            try {
733                Transformer xformer = TransformerFactory.newInstance().newTransformer();
734                CharArrayWriter outputWriter = new CharArrayWriter(8192);
735                StreamResult out = new StreamResult(outputWriter);
736
737                xformer.transform(new DOMSource(item), out);
738
739                String fragmentWithReplacedVars = evalVars(outputWriter.toString());
740
741                CharArrayReader reader = new CharArrayReader(fragmentWithReplacedVars.toCharArray());
742                tmpPref.fromXML(reader);
743            } catch (TransformerException | XMLStreamException | IOException ex) {
744                PreferencesUtils.log(ex, "Error: can not read XML fragment:");
745            }
746
747            return tmpPref;
748        }
749
750        private static String normalizeDirName(String dir) {
751            String s = dir.replace('\\', '/');
752            if (s.endsWith("/")) s = s.substring(0, s.length()-1);
753            return s;
754        }
755    }
756}
Note: See TracBrowser for help on using the repository browser.