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

Last change on this file since 12767 was 12634, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.worker, replace it by gui.MainApplication.worker + code refactoring to make sure only editor packages use it

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