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

Last change on this file was 19050, checked in by taylor.smock, 2 days 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.