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

Last change on this file was 19050, checked in by taylor.smock, 38 hours ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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