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

Last change on this file since 12855 was 12855, checked in by bastiK, 7 years ago

see #15229 - add separate interface IBaseDirectories to look up pref, user data and cache dir

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