Index: trunk/src/org/openstreetmap/josm/data/AutosaveTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/AutosaveTask.java	(revision 12630)
+++ 	(revision )
@@ -1,430 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.data;
-
-import static org.openstreetmap.josm.tools.I18n.marktr;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.lang.management.ManagementFactory;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.actions.OpenFileAction.OpenFileTask;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
-import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
-import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter.Listener;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.data.preferences.IntegerProperty;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
-import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
-import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
-import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.OsmExporter;
-import org.openstreetmap.josm.io.OsmImporter;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Saves data layers periodically so they can be recovered in case of a crash.
- *
- * There are 2 directories
- *  - autosave dir: copies of the currently open data layers are saved here every
- *      PROP_INTERVAL seconds. When a data layer is closed normally, the corresponding
- *      files are removed. If this dir is non-empty on start, JOSM assumes
- *      that it crashed last time.
- *  - deleted layers dir: "secondary archive" - when autosaved layers are restored
- *      they are copied to this directory. We cannot keep them in the autosave folder,
- *      but just deleting it would be dangerous: Maybe a feature inside the file
- *      caused JOSM to crash. If the data is valuable, the user can still try to
- *      open with another versions of JOSM or fix the problem manually.
- *
- *      The deleted layers dir keeps at most PROP_DELETED_LAYERS files.
- *
- * @since  3378 (creation)
- * @since 10386 (new LayerChangeListener interface)
- */
-public class AutosaveTask extends TimerTask implements LayerChangeListener, Listener {
-
-    private static final char[] ILLEGAL_CHARACTERS = {'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'};
-    private static final String AUTOSAVE_DIR = "autosave";
-    private static final String DELETED_LAYERS_DIR = "autosave/deleted_layers";
-
-    /**
-     * If autosave is enabled
-     */
-    public static final BooleanProperty PROP_AUTOSAVE_ENABLED = new BooleanProperty("autosave.enabled", true);
-    /**
-     * The number of files to store per layer
-     */
-    public static final IntegerProperty PROP_FILES_PER_LAYER = new IntegerProperty("autosave.filesPerLayer", 1);
-    /**
-     * How many deleted layers should be stored
-     */
-    public static final IntegerProperty PROP_DELETED_LAYERS = new IntegerProperty("autosave.deletedLayersBackupCount", 5);
-    /**
-     * The autosave interval, in seconds
-     */
-    public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("autosave.interval", (int) TimeUnit.MINUTES.toSeconds(5));
-    /**
-     * The maximum number of autosave files to store
-     */
-    public static final IntegerProperty PROP_INDEX_LIMIT = new IntegerProperty("autosave.index-limit", 1000);
-    /**
-     * Defines if a notification should be displayed after each autosave
-     */
-    public static final BooleanProperty PROP_NOTIFICATION = new BooleanProperty("autosave.notification", false);
-
-    protected static final class AutosaveLayerInfo {
-        private final OsmDataLayer layer;
-        private String layerName;
-        private String layerFileName;
-        private final Deque<File> backupFiles = new LinkedList<>();
-
-        AutosaveLayerInfo(OsmDataLayer layer) {
-            this.layer = layer;
-        }
-    }
-
-    private final DataSetListenerAdapter datasetAdapter = new DataSetListenerAdapter(this);
-    private final Set<DataSet> changedDatasets = new HashSet<>();
-    private final List<AutosaveLayerInfo> layersInfo = new ArrayList<>();
-    private final Object layersLock = new Object();
-    private final Deque<File> deletedLayers = new LinkedList<>();
-
-    private final File autosaveDir = new File(Main.pref.getUserDataDirectory(), AUTOSAVE_DIR);
-    private final File deletedLayersDir = new File(Main.pref.getUserDataDirectory(), DELETED_LAYERS_DIR);
-
-    /**
-     * Replies the autosave directory.
-     * @return the autosave directory
-     * @since 10299
-     */
-    public final Path getAutosaveDir() {
-        return autosaveDir.toPath();
-    }
-
-    /**
-     * Starts the autosave background task.
-     */
-    public void schedule() {
-        if (PROP_INTERVAL.get() > 0) {
-
-            if (!autosaveDir.exists() && !autosaveDir.mkdirs()) {
-                Logging.warn(tr("Unable to create directory {0}, autosave will be disabled", autosaveDir.getAbsolutePath()));
-                return;
-            }
-            if (!deletedLayersDir.exists() && !deletedLayersDir.mkdirs()) {
-                Logging.warn(tr("Unable to create directory {0}, autosave will be disabled", deletedLayersDir.getAbsolutePath()));
-                return;
-            }
-
-            File[] files = deletedLayersDir.listFiles();
-            if (files != null) {
-                for (File f: files) {
-                    deletedLayers.add(f); // FIXME: sort by mtime
-                }
-            }
-
-            new Timer(true).schedule(this, TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(PROP_INTERVAL.get()));
-            Main.getLayerManager().addAndFireLayerChangeListener(this);
-        }
-    }
-
-    private static String getFileName(String layerName, int index) {
-        String result = layerName;
-        for (char illegalCharacter : ILLEGAL_CHARACTERS) {
-            result = result.replaceAll(Pattern.quote(String.valueOf(illegalCharacter)),
-                    '&' + String.valueOf((int) illegalCharacter) + ';');
-        }
-        if (index != 0) {
-            result = result + '_' + index;
-        }
-        return result;
-    }
-
-    private void setLayerFileName(AutosaveLayerInfo layer) {
-        int index = 0;
-        while (true) {
-            String filename = getFileName(layer.layer.getName(), index);
-            boolean foundTheSame = false;
-            for (AutosaveLayerInfo info: layersInfo) {
-                if (info != layer && filename.equals(info.layerFileName)) {
-                    foundTheSame = true;
-                    break;
-                }
-            }
-
-            if (!foundTheSame) {
-                layer.layerFileName = filename;
-                return;
-            }
-
-            index++;
-        }
-    }
-
-    protected File getNewLayerFile(AutosaveLayerInfo layer, Date now, int startIndex) {
-        int index = startIndex;
-        while (true) {
-            String filename = String.format("%1$s_%2$tY%2$tm%2$td_%2$tH%2$tM%2$tS%2$tL%3$s",
-                    layer.layerFileName, now, index == 0 ? "" : ('_' + Integer.toString(index)));
-            File result = new File(autosaveDir, filename + '.' + Main.pref.get("autosave.extension", "osm"));
-            try {
-                if (index > PROP_INDEX_LIMIT.get())
-                    throw new IOException("index limit exceeded");
-                if (result.createNewFile()) {
-                    createNewPidFile(autosaveDir, filename);
-                    return result;
-                } else {
-                    Logging.warn(tr("Unable to create file {0}, other filename will be used", result.getAbsolutePath()));
-                }
-            } catch (IOException e) {
-                Logging.log(Logging.LEVEL_ERROR, tr("IOError while creating file, autosave will be skipped: {0}", e.getMessage()), e);
-                return null;
-            }
-            index++;
-        }
-    }
-
-    private static void createNewPidFile(File autosaveDir, String filename) {
-        File pidFile = new File(autosaveDir, filename+".pid");
-        try (PrintStream ps = new PrintStream(pidFile, "UTF-8")) {
-            ps.println(ManagementFactory.getRuntimeMXBean().getName());
-        } catch (IOException | SecurityException t) {
-            Logging.error(t);
-        }
-    }
-
-    private void savelayer(AutosaveLayerInfo info) {
-        if (!info.layer.getName().equals(info.layerName)) {
-            setLayerFileName(info);
-            info.layerName = info.layer.getName();
-        }
-        if (changedDatasets.remove(info.layer.data)) {
-            File file = getNewLayerFile(info, new Date(), 0);
-            if (file != null) {
-                info.backupFiles.add(file);
-                new OsmExporter().exportData(file, info.layer, true /* no backup with appended ~ */);
-            }
-        }
-        while (info.backupFiles.size() > PROP_FILES_PER_LAYER.get()) {
-            File oldFile = info.backupFiles.remove();
-            if (Utils.deleteFile(oldFile, marktr("Unable to delete old backup file {0}"))) {
-                Utils.deleteFile(getPidFile(oldFile), marktr("Unable to delete old backup file {0}"));
-            }
-        }
-    }
-
-    @Override
-    public void run() {
-        synchronized (layersLock) {
-            try {
-                for (AutosaveLayerInfo info: layersInfo) {
-                    savelayer(info);
-                }
-                changedDatasets.clear();
-                if (PROP_NOTIFICATION.get() && !layersInfo.isEmpty()) {
-                    GuiHelper.runInEDT(this::displayNotification);
-                }
-            } catch (RuntimeException t) { // NOPMD
-                // Don't let exception stop time thread
-                Logging.error("Autosave failed:");
-                Logging.error(t);
-            }
-        }
-    }
-
-    protected void displayNotification() {
-        new Notification(tr("Your work has been saved automatically."))
-        .setDuration(Notification.TIME_SHORT)
-        .show();
-    }
-
-    @Override
-    public void layerOrderChanged(LayerOrderChangeEvent e) {
-        // Do nothing
-    }
-
-    private void registerNewlayer(OsmDataLayer layer) {
-        synchronized (layersLock) {
-            layer.data.addDataSetListener(datasetAdapter);
-            layersInfo.add(new AutosaveLayerInfo(layer));
-        }
-    }
-
-    @Override
-    public void layerAdded(LayerAddEvent e) {
-        if (e.getAddedLayer() instanceof OsmDataLayer) {
-            registerNewlayer((OsmDataLayer) e.getAddedLayer());
-        }
-    }
-
-    @Override
-    public void layerRemoving(LayerRemoveEvent e) {
-        if (e.getRemovedLayer() instanceof OsmDataLayer) {
-            synchronized (layersLock) {
-                OsmDataLayer osmLayer = (OsmDataLayer) e.getRemovedLayer();
-                osmLayer.data.removeDataSetListener(datasetAdapter);
-                Iterator<AutosaveLayerInfo> it = layersInfo.iterator();
-                while (it.hasNext()) {
-                    AutosaveLayerInfo info = it.next();
-                    if (info.layer == osmLayer) {
-
-                        savelayer(info);
-                        File lastFile = info.backupFiles.pollLast();
-                        if (lastFile != null) {
-                            moveToDeletedLayersFolder(lastFile);
-                        }
-                        for (File file: info.backupFiles) {
-                            if (Utils.deleteFile(file)) {
-                                Utils.deleteFile(getPidFile(file));
-                            }
-                        }
-
-                        it.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public void processDatasetEvent(AbstractDatasetChangedEvent event) {
-        changedDatasets.add(event.getDataset());
-    }
-
-    protected File getPidFile(File osmFile) {
-        return new File(autosaveDir, osmFile.getName().replaceFirst("[.][^.]+$", ".pid"));
-    }
-
-    /**
-     * Replies the list of .osm files still present in autosave dir, that are not currently managed by another instance of JOSM.
-     * These files are hence unsaved layers from an old instance of JOSM that crashed and may be recovered by this instance.
-     * @return The list of .osm files still present in autosave dir, that are not currently managed by another instance of JOSM
-     */
-    public List<File> getUnsavedLayersFiles() {
-        List<File> result = new ArrayList<>();
-        File[] files = autosaveDir.listFiles(OsmImporter.FILE_FILTER);
-        if (files == null)
-            return result;
-        for (File file: files) {
-            if (file.isFile()) {
-                boolean skipFile = false;
-                File pidFile = getPidFile(file);
-                if (pidFile.exists()) {
-                    try (BufferedReader reader = Files.newBufferedReader(pidFile.toPath(), StandardCharsets.UTF_8)) {
-                        String jvmId = reader.readLine();
-                        if (jvmId != null) {
-                            String pid = jvmId.split("@")[0];
-                            skipFile = jvmPerfDataFileExists(pid);
-                        }
-                    } catch (IOException | SecurityException t) {
-                        Logging.error(t);
-                    }
-                }
-                if (!skipFile) {
-                    result.add(file);
-                }
-            }
-        }
-        return result;
-    }
-
-    private static boolean jvmPerfDataFileExists(final String jvmId) {
-        File jvmDir = new File(System.getProperty("java.io.tmpdir") + File.separator + "hsperfdata_" + System.getProperty("user.name"));
-        if (jvmDir.exists() && jvmDir.canRead()) {
-            File[] files = jvmDir.listFiles((FileFilter) file -> file.getName().equals(jvmId) && file.isFile());
-            return files != null && files.length == 1;
-        }
-        return false;
-    }
-
-    /**
-     * Recover the unsaved layers and open them asynchronously.
-     * @return A future that can be used to wait for the completion of this task.
-     */
-    public Future<?> recoverUnsavedLayers() {
-        List<File> files = getUnsavedLayersFiles();
-        final OpenFileTask openFileTsk = new OpenFileTask(files, null, tr("Restoring files"));
-        final Future<?> openFilesFuture = Main.worker.submit(openFileTsk);
-        return Main.worker.submit(() -> {
-            try {
-                // Wait for opened tasks to be generated.
-                openFilesFuture.get();
-                for (File f: openFileTsk.getSuccessfullyOpenedFiles()) {
-                    moveToDeletedLayersFolder(f);
-                }
-            } catch (InterruptedException | ExecutionException e) {
-                Logging.error(e);
-            }
-        });
-    }
-
-    /**
-     * Move file to the deleted layers directory.
-     * If moving does not work, it will try to delete the file directly.
-     * Afterwards, if the number of deleted layers gets larger than PROP_DELETED_LAYERS,
-     * some files in the deleted layers directory will be removed.
-     *
-     * @param f the file, usually from the autosave dir
-     */
-    private void moveToDeletedLayersFolder(File f) {
-        File backupFile = new File(deletedLayersDir, f.getName());
-        File pidFile = getPidFile(f);
-
-        if (backupFile.exists()) {
-            deletedLayers.remove(backupFile);
-            Utils.deleteFile(backupFile, marktr("Unable to delete old backup file {0}"));
-        }
-        if (f.renameTo(backupFile)) {
-            deletedLayers.add(backupFile);
-            Utils.deleteFile(pidFile);
-        } else {
-            Logging.warn(String.format("Could not move autosaved file %s to %s folder", f.getName(), deletedLayersDir.getName()));
-            // we cannot move to deleted folder, so just try to delete it directly
-            if (Utils.deleteFile(f, marktr("Unable to delete backup file {0}"))) {
-                Utils.deleteFile(pidFile, marktr("Unable to delete PID file {0}"));
-            }
-        }
-        while (deletedLayers.size() > PROP_DELETED_LAYERS.get()) {
-            File next = deletedLayers.remove();
-            if (next == null) {
-                break;
-            }
-            Utils.deleteFile(next, marktr("Unable to delete archived backup file {0}"));
-        }
-    }
-
-    /**
-     * Mark all unsaved layers as deleted. They are still preserved in the deleted layers folder.
-     */
-    public void discardUnsavedLayers() {
-        for (File f: getUnsavedLayersFiles()) {
-            moveToDeletedLayersFolder(f);
-        }
-    }
-}
Index: trunk/src/org/openstreetmap/josm/data/CustomConfigurator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/CustomConfigurator.java	(revision 12630)
+++ 	(revision )
@@ -1,1164 +1,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.data;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.CharArrayReader;
-import java.io.CharArrayWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-import org.openstreetmap.josm.Main;
-import org.openstreetmap.josm.data.preferences.ListListSetting;
-import org.openstreetmap.josm.data.preferences.ListSetting;
-import org.openstreetmap.josm.data.preferences.MapListSetting;
-import org.openstreetmap.josm.data.preferences.Setting;
-import org.openstreetmap.josm.data.preferences.StringSetting;
-import org.openstreetmap.josm.gui.io.DownloadFileTask;
-import org.openstreetmap.josm.plugins.PluginDownloadTask;
-import org.openstreetmap.josm.plugins.PluginInformation;
-import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
-import org.openstreetmap.josm.tools.LanguageInfo;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-import org.w3c.dom.DOMException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-/**
- * Class to process configuration changes stored in XML
- * can be used to modify preferences, store/delete files in .josm folders etc
- */
-public final class CustomConfigurator {
-
-    private static StringBuilder summary = new StringBuilder();
-
-    private CustomConfigurator() {
-        // Hide default constructor for utils classes
-    }
-
-    /**
-     * Log a formatted message.
-     * @param fmt format
-     * @param vars arguments
-     * @see String#format
-     */
-    public static void log(String fmt, Object... vars) {
-        summary.append(String.format(fmt, vars));
-    }
-
-    /**
-     * Log a message.
-     * @param s message to log
-     */
-    public static void log(String s) {
-        summary.append(s).append('\n');
-    }
-
-    /**
-     * Log an exception.
-     * @param e exception to log
-     * @param s message prefix
-     * @since 10469
-     */
-    public static void log(Exception e, String s) {
-        summary.append(s).append(' ').append(Logging.getErrorMessage(e)).append('\n');
-    }
-
-    /**
-     * Returns the log.
-     * @return the log
-     */
-    public static String getLog() {
-        return summary.toString();
-    }
-
-    /**
-     * Resets the log.
-     */
-    public static void resetLog() {
-        summary = new StringBuilder();
-    }
-
-    /**
-     * Read configuration script from XML file, modifying main preferences
-     * @param dir - directory
-     * @param fileName - XML file name
-     */
-    public static void readXML(String dir, String fileName) {
-        readXML(new File(dir, fileName));
-    }
-
-    /**
-     * Read configuration script from XML file, modifying given preferences object
-     * @param file - file to open for reading XML
-     * @param prefs - arbitrary Preferences object to modify by script
-     */
-    public static void readXML(final File file, final Preferences prefs) {
-        synchronized (CustomConfigurator.class) {
-            busy = true;
-        }
-        new XMLCommandProcessor(prefs).openAndReadXML(file);
-        synchronized (CustomConfigurator.class) {
-            CustomConfigurator.class.notifyAll();
-            busy = false;
-        }
-    }
-
-    /**
-     * Read configuration script from XML file, modifying main preferences
-     * @param file - file to open for reading XML
-     */
-    public static void readXML(File file) {
-        readXML(file, Main.pref);
-    }
-
-    /**
-     * Downloads file to one of JOSM standard folders
-     * @param address - URL to download
-     * @param path - file path relative to base where to put downloaded file
-     * @param base - only "prefs", "cache" and "plugins" allowed for standard folders
-     */
-    public static void downloadFile(String address, String path, String base) {
-        processDownloadOperation(address, path, getDirectoryByAbbr(base), true, false);
-    }
-
-    /**
-     * Downloads file to one of JOSM standard folders and unpack it as ZIP/JAR file
-     * @param address - URL to download
-     * @param path - file path relative to base where to put downloaded file
-     * @param base - only "prefs", "cache" and "plugins" allowed for standard folders
-     */
-    public static void downloadAndUnpackFile(String address, String path, String base) {
-        processDownloadOperation(address, path, getDirectoryByAbbr(base), true, true);
-    }
-
-    /**
-     * Downloads file to arbitrary folder
-     * @param address - URL to download
-     * @param path - file path relative to parentDir where to put downloaded file
-     * @param parentDir - folder where to put file
-     * @param mkdir - if true, non-existing directories will be created
-     * @param unzip - if true file wil be unzipped and deleted after download
-     */
-    public static void processDownloadOperation(String address, String path, String parentDir, boolean mkdir, boolean unzip) {
-        String dir = parentDir;
-        if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
-            return; // some basic protection
-        }
-        File fOut = new File(dir, path);
-        DownloadFileTask downloadFileTask = new DownloadFileTask(Main.parent, address, fOut, mkdir, unzip);
-
-        Main.worker.submit(downloadFileTask);
-        log("Info: downloading file from %s to %s in background ", parentDir, fOut.getAbsolutePath());
-        if (unzip) log("and unpacking it"); else log("");
-
-    }
-
-    /**
-     * Simple function to show messageBox, may be used from JS API and from other code
-     * @param type - 'i','w','e','q','p' for Information, Warning, Error, Question, Message
-     * @param text - message to display, HTML allowed
-     */
-    public static void messageBox(String type, String text) {
-        char c = (type == null || type.isEmpty() ? "plain" : type).charAt(0);
-        switch (c) {
-            case 'i': JOptionPane.showMessageDialog(Main.parent, text, tr("Information"), JOptionPane.INFORMATION_MESSAGE); break;
-            case 'w': JOptionPane.showMessageDialog(Main.parent, text, tr("Warning"), JOptionPane.WARNING_MESSAGE); break;
-            case 'e': JOptionPane.showMessageDialog(Main.parent, text, tr("Error"), JOptionPane.ERROR_MESSAGE); break;
-            case 'q': JOptionPane.showMessageDialog(Main.parent, text, tr("Question"), JOptionPane.QUESTION_MESSAGE); break;
-            case 'p': JOptionPane.showMessageDialog(Main.parent, text, tr("Message"), JOptionPane.PLAIN_MESSAGE); break;
-            default: Logging.warn("Unsupported messageBox type: " + c);
-        }
-    }
-
-    /**
-     * Simple function for choose window, may be used from JS API and from other code
-     * @param text - message to show, HTML allowed
-     * @param opts -
-     * @return number of pressed button, -1 if cancelled
-     */
-    public static int askForOption(String text, String opts) {
-        if (!opts.isEmpty()) {
-            return JOptionPane.showOptionDialog(Main.parent, text, "Question",
-                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, opts.split(";"), 0);
-        } else {
-            return JOptionPane.showOptionDialog(Main.parent, text, "Question",
-                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, 2);
-        }
-    }
-
-    public static String askForText(String text) {
-        String s = JOptionPane.showInputDialog(Main.parent, text, tr("Enter text"), JOptionPane.QUESTION_MESSAGE);
-        return s != null ? s.trim() : null;
-    }
-
-    /**
-     * This function exports part of user preferences to specified file.
-     * Default values are not saved.
-     * @param filename - where to export
-     * @param append - if true, resulting file cause appending to exuisting preferences
-     * @param keys - which preferences keys you need to export ("imagery.entries", for example)
-     */
-    public static void exportPreferencesKeysToFile(String filename, boolean append, String... keys) {
-        Set<String> keySet = new HashSet<>();
-        Collections.addAll(keySet, keys);
-        exportPreferencesKeysToFile(filename, append, keySet);
-    }
-
-    /**
-     * This function exports part of user preferences to specified file.
-     * Default values are not saved.
-     * Preference keys matching specified pattern are saved
-     * @param fileName - where to export
-     * @param append - if true, resulting file cause appending to exuisting preferences
-     * @param pattern - Regexp pattern forh preferences keys you need to export (".*imagery.*", for example)
-     */
-    public static void exportPreferencesKeysByPatternToFile(String fileName, boolean append, String pattern) {
-        List<String> keySet = new ArrayList<>();
-        Map<String, Setting<?>> allSettings = Main.pref.getAllSettings();
-        for (String key: allSettings.keySet()) {
-            if (key.matches(pattern))
-                keySet.add(key);
-        }
-        exportPreferencesKeysToFile(fileName, append, keySet);
-    }
-
-    /**
-     * Export specified preferences keys to configuration file
-     * @param filename - name of file
-     * @param append - will the preferences be appended to existing ones when file is imported later.
-     * Elsewhere preferences from file will replace existing keys.
-     * @param keys - collection of preferences key names to save
-     */
-    public static void exportPreferencesKeysToFile(String filename, boolean append, Collection<String> keys) {
-        Element root = null;
-        Document document = null;
-        Document exportDocument = null;
-
-        try {
-            String toXML = Main.pref.toXML(true);
-            DocumentBuilder builder = Utils.newSafeDOMBuilder();
-            document = builder.parse(new ByteArrayInputStream(toXML.getBytes(StandardCharsets.UTF_8)));
-            exportDocument = builder.newDocument();
-            root = document.getDocumentElement();
-        } catch (SAXException | IOException | ParserConfigurationException ex) {
-            Logging.log(Logging.LEVEL_WARN, "Error getting preferences to save:", ex);
-        }
-        if (root == null || exportDocument == null)
-            return;
-        try {
-            Element newRoot = exportDocument.createElement("config");
-            exportDocument.appendChild(newRoot);
-
-            Element prefElem = exportDocument.createElement("preferences");
-            prefElem.setAttribute("operation", append ? "append" : "replace");
-            newRoot.appendChild(prefElem);
-
-            NodeList childNodes = root.getChildNodes();
-            int n = childNodes.getLength();
-            for (int i = 0; i < n; i++) {
-                Node item = childNodes.item(i);
-                if (item.getNodeType() == Node.ELEMENT_NODE) {
-                    String currentKey = ((Element) item).getAttribute("key");
-                    if (keys.contains(currentKey)) {
-                        Node imported = exportDocument.importNode(item, true);
-                        prefElem.appendChild(imported);
-                    }
-                }
-            }
-            File f = new File(filename);
-            Transformer ts = TransformerFactory.newInstance().newTransformer();
-            ts.setOutputProperty(OutputKeys.INDENT, "yes");
-            ts.transform(new DOMSource(exportDocument), new StreamResult(f.toURI().getPath()));
-        } catch (DOMException | TransformerFactoryConfigurationError | TransformerException ex) {
-            Logging.warn("Error saving preferences part:");
-            Logging.error(ex);
-        }
-    }
-
-    public static void deleteFile(String path, String base) {
-        String dir = getDirectoryByAbbr(base);
-        if (dir == null) {
-            log("Error: Can not find base, use base=cache, base=prefs or base=plugins attribute.");
-            return;
-        }
-        log("Delete file: %s\n", path);
-        if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
-            return; // some basic protection
-        }
-        File fOut = new File(dir, path);
-        if (fOut.exists()) {
-            deleteFileOrDirectory(fOut);
-        }
-    }
-
-    public static void deleteFileOrDirectory(File f) {
-        if (f.isDirectory()) {
-            File[] files = f.listFiles();
-            if (files != null) {
-                for (File f1: files) {
-                    deleteFileOrDirectory(f1);
-                }
-            }
-        }
-        if (!Utils.deleteFile(f)) {
-            log("Warning: Can not delete file "+f.getPath());
-        }
-    }
-
-    private static boolean busy;
-
-    public static void pluginOperation(String install, String uninstall, String delete) {
-        final List<String> installList = new ArrayList<>();
-        final List<String> removeList = new ArrayList<>();
-        final List<String> deleteList = new ArrayList<>();
-        Collections.addAll(installList, install.toLowerCase(Locale.ENGLISH).split(";"));
-        Collections.addAll(removeList, uninstall.toLowerCase(Locale.ENGLISH).split(";"));
-        Collections.addAll(deleteList, delete.toLowerCase(Locale.ENGLISH).split(";"));
-        installList.remove("");
-        removeList.remove("");
-        deleteList.remove("");
-
-        if (!installList.isEmpty()) {
-            log("Plugins install: "+installList);
-        }
-        if (!removeList.isEmpty()) {
-            log("Plugins turn off: "+removeList);
-        }
-        if (!deleteList.isEmpty()) {
-            log("Plugins delete: "+deleteList);
-        }
-
-        final ReadLocalPluginInformationTask task = new ReadLocalPluginInformationTask();
-        Runnable r = () -> {
-            if (task.isCanceled()) return;
-            synchronized (CustomConfigurator.class) {
-                try { // proceed only after all other tasks were finished
-                    while (busy) CustomConfigurator.class.wait();
-                } catch (InterruptedException ex) {
-                    Logging.log(Logging.LEVEL_WARN, "InterruptedException while reading local plugin information", ex);
-                    Thread.currentThread().interrupt();
-                }
-
-                SwingUtilities.invokeLater(() -> {
-                    List<PluginInformation> availablePlugins = task.getAvailablePlugins();
-                    List<PluginInformation> toInstallPlugins = new ArrayList<>();
-                    List<PluginInformation> toRemovePlugins = new ArrayList<>();
-                    List<PluginInformation> toDeletePlugins = new ArrayList<>();
-                    for (PluginInformation pi1: availablePlugins) {
-                        String name = pi1.name.toLowerCase(Locale.ENGLISH);
-                        if (installList.contains(name)) toInstallPlugins.add(pi1);
-                        if (removeList.contains(name)) toRemovePlugins.add(pi1);
-                        if (deleteList.contains(name)) toDeletePlugins.add(pi1);
-                    }
-                    if (!installList.isEmpty()) {
-                        PluginDownloadTask pluginDownloadTask =
-                                new PluginDownloadTask(Main.parent, toInstallPlugins, tr("Installing plugins"));
-                        Main.worker.submit(pluginDownloadTask);
-                    }
-                    Collection<String> pls = new ArrayList<>(Main.pref.getCollection("plugins"));
-                    for (PluginInformation pi2: toInstallPlugins) {
-                        if (!pls.contains(pi2.name)) {
-                            pls.add(pi2.name);
-                        }
-                    }
-                    for (PluginInformation pi3: toRemovePlugins) {
-                        pls.remove(pi3.name);
-                    }
-                    for (PluginInformation pi4: toDeletePlugins) {
-                        pls.remove(pi4.name);
-                        new File(Main.pref.getPluginsDirectory(), pi4.name+".jar").deleteOnExit();
-                    }
-                    Main.pref.putCollection("plugins", pls);
-                });
-            }
-        };
-        Main.worker.submit(task);
-        Main.worker.submit(r);
-    }
-
-    private static String getDirectoryByAbbr(String base) {
-        String dir;
-        if ("prefs".equals(base) || base.isEmpty()) {
-            dir = Main.pref.getPreferencesDirectory().getAbsolutePath();
-        } else if ("cache".equals(base)) {
-            dir = Main.pref.getCacheDirectory().getAbsolutePath();
-        } else if ("plugins".equals(base)) {
-            dir = Main.pref.getPluginsDirectory().getAbsolutePath();
-        } else {
-            dir = null;
-        }
-        return dir;
-    }
-
-    public static Preferences clonePreferences(Preferences pref) {
-        Preferences tmp = new Preferences();
-        tmp.settingsMap.putAll(pref.settingsMap);
-        tmp.defaultsMap.putAll(pref.defaultsMap);
-        tmp.colornames.putAll(pref.colornames);
-
-        return tmp;
-    }
-
-    public static class XMLCommandProcessor {
-
-        private Preferences mainPrefs;
-        private final Map<String, Element> tasksMap = new HashMap<>();
-
-        private boolean lastV; // last If condition result
-
-        private ScriptEngine engine;
-
-        public void openAndReadXML(File file) {
-            log("-- Reading custom preferences from " + file.getAbsolutePath() + " --");
-            try {
-                String fileDir = file.getParentFile().getAbsolutePath();
-                if (fileDir != null) engine.eval("scriptDir='"+normalizeDirName(fileDir) +"';");
-                try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
-                    openAndReadXML(is);
-                }
-            } catch (ScriptException | IOException | SecurityException ex) {
-                log(ex, "Error reading custom preferences:");
-            }
-        }
-
-        public void openAndReadXML(InputStream is) {
-            try {
-                Document document = Utils.parseSafeDOM(is);
-                synchronized (CustomConfigurator.class) {
-                    processXML(document);
-                }
-            } catch (SAXException | IOException | ParserConfigurationException ex) {
-                log(ex, "Error reading custom preferences:");
-            }
-            log("-- Reading complete --");
-        }
-
-        public XMLCommandProcessor(Preferences mainPrefs) {
-            try {
-                this.mainPrefs = mainPrefs;
-                resetLog();
-                engine = new ScriptEngineManager().getEngineByName("JavaScript");
-                engine.eval("API={}; API.pref={}; API.fragments={};");
-
-                engine.eval("homeDir='"+normalizeDirName(Main.pref.getPreferencesDirectory().getAbsolutePath()) +"';");
-                engine.eval("josmVersion="+Version.getInstance().getVersion()+';');
-                String className = CustomConfigurator.class.getName();
-                engine.eval("API.messageBox="+className+".messageBox");
-                engine.eval("API.askText=function(text) { return String("+className+".askForText(text));}");
-                engine.eval("API.askOption="+className+".askForOption");
-                engine.eval("API.downloadFile="+className+".downloadFile");
-                engine.eval("API.downloadAndUnpackFile="+className+".downloadAndUnpackFile");
-                engine.eval("API.deleteFile="+className+".deleteFile");
-                engine.eval("API.plugin ="+className+".pluginOperation");
-                engine.eval("API.pluginInstall = function(names) { "+className+".pluginOperation(names,'','');}");
-                engine.eval("API.pluginUninstall = function(names) { "+className+".pluginOperation('',names,'');}");
-                engine.eval("API.pluginDelete = function(names) { "+className+".pluginOperation('','',names);}");
-            } catch (ScriptException ex) {
-                log("Error: initializing script engine: "+ex.getMessage());
-                Logging.error(ex);
-            }
-        }
-
-        private void processXML(Document document) {
-            processXmlFragment(document.getDocumentElement());
-        }
-
-        private void processXmlFragment(Element root) {
-            NodeList childNodes = root.getChildNodes();
-            int nops = childNodes.getLength();
-            for (int i = 0; i < nops; i++) {
-                Node item = childNodes.item(i);
-                if (item.getNodeType() != Node.ELEMENT_NODE) continue;
-                String elementName = item.getNodeName();
-                Element elem = (Element) item;
-
-                switch(elementName) {
-                case "var":
-                    setVar(elem.getAttribute("name"), evalVars(elem.getAttribute("value")));
-                    break;
-                case "task":
-                    tasksMap.put(elem.getAttribute("name"), elem);
-                    break;
-                case "runtask":
-                    if (processRunTaskElement(elem)) return;
-                    break;
-                case "ask":
-                    processAskElement(elem);
-                    break;
-                case "if":
-                    processIfElement(elem);
-                    break;
-                case "else":
-                    processElseElement(elem);
-                    break;
-                case "break":
-                    return;
-                case "plugin":
-                    processPluginInstallElement(elem);
-                    break;
-                case "messagebox":
-                    processMsgBoxElement(elem);
-                    break;
-                case "preferences":
-                    processPreferencesElement(elem);
-                    break;
-                case "download":
-                    processDownloadElement(elem);
-                    break;
-                case "delete":
-                    processDeleteElement(elem);
-                    break;
-                case "script":
-                    processScriptElement(elem);
-                    break;
-                default:
-                    log("Error: Unknown element " + elementName);
-                }
-            }
-        }
-
-        private void processPreferencesElement(Element item) {
-            String oper = evalVars(item.getAttribute("operation"));
-            String id = evalVars(item.getAttribute("id"));
-
-            if ("delete-keys".equals(oper)) {
-                String pattern = evalVars(item.getAttribute("pattern"));
-                String key = evalVars(item.getAttribute("key"));
-                PreferencesUtils.deletePreferenceKey(key, mainPrefs);
-                PreferencesUtils.deletePreferenceKeyByPattern(pattern, mainPrefs);
-                return;
-            }
-
-            Preferences tmpPref = readPreferencesFromDOMElement(item);
-            PreferencesUtils.showPrefs(tmpPref);
-
-            if (!id.isEmpty()) {
-                try {
-                    String fragmentVar = "API.fragments['"+id+"']";
-                    engine.eval(fragmentVar+"={};");
-                    PreferencesUtils.loadPrefsToJS(engine, tmpPref, fragmentVar, false);
-                    // we store this fragment as API.fragments['id']
-                } catch (ScriptException ex) {
-                    log(ex, "Error: can not load preferences fragment:");
-                }
-            }
-
-            if ("replace".equals(oper)) {
-                log("Preferences replace: %d keys: %s\n",
-                   tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString());
-                PreferencesUtils.replacePreferences(tmpPref, mainPrefs);
-            } else if ("append".equals(oper)) {
-                log("Preferences append: %d keys: %s\n",
-                   tmpPref.getAllSettings().size(), tmpPref.getAllSettings().keySet().toString());
-                PreferencesUtils.appendPreferences(tmpPref, mainPrefs);
-            } else if ("delete-values".equals(oper)) {
-                PreferencesUtils.deletePreferenceValues(tmpPref, mainPrefs);
-            }
-        }
-
-         private void processDeleteElement(Element item) {
-            String path = evalVars(item.getAttribute("path"));
-            String base = evalVars(item.getAttribute("base"));
-            deleteFile(path, base);
-        }
-
-        private void processDownloadElement(Element item) {
-            String base = evalVars(item.getAttribute("base"));
-            String dir = getDirectoryByAbbr(base);
-            if (dir == null) {
-                log("Error: Can not find directory to place file, use base=cache, base=prefs or base=plugins attribute.");
-                return;
-            }
-
-            String path = evalVars(item.getAttribute("path"));
-            if (path.contains("..") || path.startsWith("/") || path.contains(":")) {
-                return; // some basic protection
-            }
-
-            String address = evalVars(item.getAttribute("url"));
-            if (address.isEmpty() || path.isEmpty()) {
-                log("Error: Please specify url=\"where to get file\" and path=\"where to place it\"");
-                return;
-            }
-
-            String unzip = evalVars(item.getAttribute("unzip"));
-            String mkdir = evalVars(item.getAttribute("mkdir"));
-            processDownloadOperation(address, path, dir, "true".equals(mkdir), "true".equals(unzip));
-        }
-
-        private static void processPluginInstallElement(Element elem) {
-            String install = elem.getAttribute("install");
-            String uninstall = elem.getAttribute("remove");
-            String delete = elem.getAttribute("delete");
-            pluginOperation(install, uninstall, delete);
-        }
-
-        private void processMsgBoxElement(Element elem) {
-            String text = evalVars(elem.getAttribute("text"));
-            String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text"));
-            if (!locText.isEmpty()) text = locText;
-
-            String type = evalVars(elem.getAttribute("type"));
-            messageBox(type, text);
-        }
-
-        private void processAskElement(Element elem) {
-            String text = evalVars(elem.getAttribute("text"));
-            String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text"));
-            if (!locText.isEmpty()) text = locText;
-            String var = elem.getAttribute("var");
-            if (var.isEmpty()) var = "result";
-
-            String input = evalVars(elem.getAttribute("input"));
-            if ("true".equals(input)) {
-                setVar(var, askForText(text));
-            } else {
-                String opts = evalVars(elem.getAttribute("options"));
-                String locOpts = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".options"));
-                if (!locOpts.isEmpty()) opts = locOpts;
-                setVar(var, String.valueOf(askForOption(text, opts)));
-            }
-        }
-
-        public void setVar(String name, String value) {
-            try {
-                engine.eval(name+"='"+value+"';");
-            } catch (ScriptException ex) {
-                log(ex, String.format("Error: Can not assign variable: %s=%s :", name, value));
-            }
-        }
-
-        private void processIfElement(Element elem) {
-            String realValue = evalVars(elem.getAttribute("test"));
-            boolean v = false;
-            if ("true".equals(realValue) || "false".equals(realValue)) {
-                processXmlFragment(elem);
-                v = true;
-            } else {
-                log("Error: Illegal test expression in if: %s=%s\n", elem.getAttribute("test"), realValue);
-            }
-
-            lastV = v;
-        }
-
-        private void processElseElement(Element elem) {
-            if (!lastV) {
-                processXmlFragment(elem);
-            }
-        }
-
-        private boolean processRunTaskElement(Element elem) {
-            String taskName = elem.getAttribute("name");
-            Element task = tasksMap.get(taskName);
-            if (task != null) {
-                log("EXECUTING TASK "+taskName);
-                processXmlFragment(task); // process task recursively
-            } else {
-                log("Error: Can not execute task "+taskName);
-                return true;
-            }
-            return false;
-        }
-
-        private void processScriptElement(Element elem) {
-            String js = elem.getChildNodes().item(0).getTextContent();
-            log("Processing script...");
-            try {
-                PreferencesUtils.modifyPreferencesByScript(engine, mainPrefs, js);
-            } catch (ScriptException ex) {
-                messageBox("e", ex.getMessage());
-                log(ex, "JS error:");
-            }
-            log("Script finished");
-        }
-
-        /**
-         * substitute ${expression} = expression evaluated by JavaScript
-         * @param s string
-         * @return evaluation result
-         */
-        private String evalVars(String s) {
-            Matcher mr = Pattern.compile("\\$\\{([^\\}]*)\\}").matcher(s);
-            StringBuffer sb = new StringBuffer();
-            while (mr.find()) {
-                try {
-                    String result = engine.eval(mr.group(1)).toString();
-                    mr.appendReplacement(sb, result);
-                } catch (ScriptException ex) {
-                    log(ex, String.format("Error: Can not evaluate expression %s :", mr.group(1)));
-                }
-            }
-            mr.appendTail(sb);
-            return sb.toString();
-        }
-
-        private Preferences readPreferencesFromDOMElement(Element item) {
-            Preferences tmpPref = new Preferences();
-            try {
-                Transformer xformer = TransformerFactory.newInstance().newTransformer();
-                CharArrayWriter outputWriter = new CharArrayWriter(8192);
-                StreamResult out = new StreamResult(outputWriter);
-
-                xformer.transform(new DOMSource(item), out);
-
-                String fragmentWithReplacedVars = evalVars(outputWriter.toString());
-
-                CharArrayReader reader = new CharArrayReader(fragmentWithReplacedVars.toCharArray());
-                tmpPref.fromXML(reader);
-            } catch (TransformerException | XMLStreamException | IOException ex) {
-                log(ex, "Error: can not read XML fragment:");
-            }
-
-            return tmpPref;
-        }
-
-        private static String normalizeDirName(String dir) {
-            String s = dir.replace('\\', '/');
-            if (s.endsWith("/")) s = s.substring(0, s.length()-1);
-            return s;
-        }
-    }
-
-    /**
-     * Helper class to do specific Preferences operation - appending, replacing,
-     * deletion by key and by value
-     * Also contains functions that convert preferences object to JavaScript object and back
-     */
-    public static final class PreferencesUtils {
-
-        private PreferencesUtils() {
-            // Hide implicit public constructor for utility class
-        }
-
-        private static void replacePreferences(Preferences fragment, Preferences mainpref) {
-            for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) {
-                mainpref.putSetting(entry.getKey(), entry.getValue());
-            }
-        }
-
-        private static void appendPreferences(Preferences fragment, Preferences mainpref) {
-            for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) {
-                String key = entry.getKey();
-                if (entry.getValue() instanceof StringSetting) {
-                    mainpref.putSetting(key, entry.getValue());
-                } else if (entry.getValue() instanceof ListSetting) {
-                    ListSetting lSetting = (ListSetting) entry.getValue();
-                    Collection<String> newItems = getCollection(mainpref, key, true);
-                    if (newItems == null) continue;
-                    for (String item : lSetting.getValue()) {
-                        // add nonexisting elements to then list
-                        if (!newItems.contains(item)) {
-                            newItems.add(item);
-                        }
-                    }
-                    mainpref.putCollection(key, newItems);
-                } else if (entry.getValue() instanceof ListListSetting) {
-                    ListListSetting llSetting = (ListListSetting) entry.getValue();
-                    Collection<Collection<String>> newLists = getArray(mainpref, key, true);
-                    if (newLists == null) continue;
-
-                    for (Collection<String> list : llSetting.getValue()) {
-                        // add nonexisting list (equals comparison for lists is used implicitly)
-                        if (!newLists.contains(list)) {
-                            newLists.add(list);
-                        }
-                    }
-                    mainpref.putArray(key, newLists);
-                } else if (entry.getValue() instanceof MapListSetting) {
-                    MapListSetting mlSetting = (MapListSetting) entry.getValue();
-                    List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
-                    if (newMaps == null) continue;
-
-                    // get existing properties as list of maps
-
-                    for (Map<String, String> map : mlSetting.getValue()) {
-                        // add nonexisting map (equals comparison for maps is used implicitly)
-                        if (!newMaps.contains(map)) {
-                            newMaps.add(map);
-                        }
-                    }
-                    mainpref.putListOfStructs(entry.getKey(), newMaps);
-                }
-            }
-        }
-
-        /**
-         * Delete items from {@code mainpref} collections that match items from {@code fragment} collections.
-         * @param fragment preferences
-         * @param mainpref main preferences
-         */
-        private static void deletePreferenceValues(Preferences fragment, Preferences mainpref) {
-
-            for (Entry<String, Setting<?>> entry : fragment.settingsMap.entrySet()) {
-                String key = entry.getKey();
-                if (entry.getValue() instanceof StringSetting) {
-                    StringSetting sSetting = (StringSetting) entry.getValue();
-                    // if mentioned value found, delete it
-                    if (sSetting.equals(mainpref.settingsMap.get(key))) {
-                        mainpref.put(key, null);
-                    }
-                } else if (entry.getValue() instanceof ListSetting) {
-                    ListSetting lSetting = (ListSetting) entry.getValue();
-                    Collection<String> newItems = getCollection(mainpref, key, true);
-                    if (newItems == null) continue;
-
-                    // remove mentioned items from collection
-                    for (String item : lSetting.getValue()) {
-                        log("Deleting preferences: from list %s: %s\n", key, item);
-                        newItems.remove(item);
-                    }
-                    mainpref.putCollection(entry.getKey(), newItems);
-                } else if (entry.getValue() instanceof ListListSetting) {
-                    ListListSetting llSetting = (ListListSetting) entry.getValue();
-                    Collection<Collection<String>> newLists = getArray(mainpref, key, true);
-                    if (newLists == null) continue;
-
-                    // if items are found in one of lists, remove that list!
-                    Iterator<Collection<String>> listIterator = newLists.iterator();
-                    while (listIterator.hasNext()) {
-                        Collection<String> list = listIterator.next();
-                        for (Collection<String> removeList : llSetting.getValue()) {
-                            if (list.containsAll(removeList)) {
-                                // remove current list, because it matches search criteria
-                                log("Deleting preferences: list from lists %s: %s\n", key, list);
-                                listIterator.remove();
-                            }
-                        }
-                    }
-
-                    mainpref.putArray(key, newLists);
-                } else if (entry.getValue() instanceof MapListSetting) {
-                    MapListSetting mlSetting = (MapListSetting) entry.getValue();
-                    List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
-                    if (newMaps == null) continue;
-
-                    Iterator<Map<String, String>> mapIterator = newMaps.iterator();
-                    while (mapIterator.hasNext()) {
-                        Map<String, String> map = mapIterator.next();
-                        for (Map<String, String> removeMap : mlSetting.getValue()) {
-                            if (map.entrySet().containsAll(removeMap.entrySet())) {
-                                // the map contain all mentioned key-value pair, so it should be deleted from "maps"
-                                log("Deleting preferences: deleting map from maps %s: %s\n", key, map);
-                                mapIterator.remove();
-                            }
-                        }
-                    }
-                    mainpref.putListOfStructs(entry.getKey(), newMaps);
-                }
-            }
-        }
-
-    private static void deletePreferenceKeyByPattern(String pattern, Preferences pref) {
-        Map<String, Setting<?>> allSettings = pref.getAllSettings();
-        for (Entry<String, Setting<?>> entry : allSettings.entrySet()) {
-            String key = entry.getKey();
-            if (key.matches(pattern)) {
-                log("Deleting preferences: deleting key from preferences: " + key);
-                pref.putSetting(key, null);
-            }
-        }
-    }
-
-    private static void deletePreferenceKey(String key, Preferences pref) {
-        Map<String, Setting<?>> allSettings = pref.getAllSettings();
-        if (allSettings.containsKey(key)) {
-            log("Deleting preferences: deleting key from preferences: " + key);
-            pref.putSetting(key, null);
-        }
-    }
-
-    private static Collection<String> getCollection(Preferences mainpref, String key, boolean warnUnknownDefault) {
-        ListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListSetting.class);
-        ListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListSetting.class);
-        if (existing == null && defaults == null) {
-            if (warnUnknownDefault) defaultUnknownWarning(key);
-            return null;
-        }
-        if (existing != null)
-            return new ArrayList<>(existing.getValue());
-        else
-            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
-    }
-
-    private static Collection<Collection<String>> getArray(Preferences mainpref, String key, boolean warnUnknownDefault) {
-        ListListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListListSetting.class);
-        ListListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListListSetting.class);
-
-        if (existing == null && defaults == null) {
-            if (warnUnknownDefault) defaultUnknownWarning(key);
-            return null;
-        }
-        if (existing != null)
-            return new ArrayList<>(existing.getValue());
-        else
-            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
-    }
-
-    private static List<Map<String, String>> getListOfStructs(Preferences mainpref, String key, boolean warnUnknownDefault) {
-        MapListSetting existing = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
-        MapListSetting defaults = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
-
-        if (existing == null && defaults == null) {
-            if (warnUnknownDefault) defaultUnknownWarning(key);
-            return null;
-        }
-
-        if (existing != null)
-            return new ArrayList<>(existing.getValue());
-        else
-            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
-    }
-
-    private static void defaultUnknownWarning(String key) {
-        log("Warning: Unknown default value of %s , skipped\n", key);
-        JOptionPane.showMessageDialog(
-                Main.parent,
-                tr("<html>Settings file asks to append preferences to <b>{0}</b>,<br/> "+
-                        "but its default value is unknown at this moment.<br/> " +
-                        "Please activate corresponding function manually and retry importing.", key),
-                tr("Warning"),
-                JOptionPane.WARNING_MESSAGE);
-    }
-
-    private static void showPrefs(Preferences tmpPref) {
-        Logging.info("properties: " + tmpPref.settingsMap);
-    }
-
-    private static void modifyPreferencesByScript(ScriptEngine engine, Preferences tmpPref, String js) throws ScriptException {
-        loadPrefsToJS(engine, tmpPref, "API.pref", true);
-        engine.eval(js);
-        readPrefsFromJS(engine, tmpPref, "API.pref");
-    }
-
-    /**
-     * Convert JavaScript preferences object to preferences data structures
-     * @param engine - JS engine to put object
-     * @param tmpPref - preferences to fill from JS
-     * @param varInJS - JS variable name, where preferences are stored
-     * @throws ScriptException if the evaluation fails
-     */
-    public static void readPrefsFromJS(ScriptEngine engine, Preferences tmpPref, String varInJS) throws ScriptException {
-        String finish =
-            "stringMap = new java.util.TreeMap ;"+
-            "listMap =  new java.util.TreeMap ;"+
-            "listlistMap = new java.util.TreeMap ;"+
-            "listmapMap =  new java.util.TreeMap ;"+
-            "for (key in "+varInJS+") {"+
-            "  val = "+varInJS+"[key];"+
-            "  type = typeof val == 'string' ? 'string' : val.type;"+
-            "  if (type == 'string') {"+
-            "    stringMap.put(key, val);"+
-            "  } else if (type == 'list') {"+
-            "    l = new java.util.ArrayList;"+
-            "    for (i=0; i<val.length; i++) {"+
-            "      l.add(java.lang.String.valueOf(val[i]));"+
-            "    }"+
-            "    listMap.put(key, l);"+
-            "  } else if (type == 'listlist') {"+
-            "    l = new java.util.ArrayList;"+
-            "    for (i=0; i<val.length; i++) {"+
-            "      list=val[i];"+
-            "      jlist=new java.util.ArrayList;"+
-            "      for (j=0; j<list.length; j++) {"+
-            "         jlist.add(java.lang.String.valueOf(list[j]));"+
-            "      }"+
-            "      l.add(jlist);"+
-            "    }"+
-            "    listlistMap.put(key, l);"+
-            "  } else if (type == 'listmap') {"+
-            "    l = new java.util.ArrayList;"+
-            "    for (i=0; i<val.length; i++) {"+
-            "      map=val[i];"+
-            "      jmap=new java.util.TreeMap;"+
-            "      for (var key2 in map) {"+
-            "         jmap.put(key2,java.lang.String.valueOf(map[key2]));"+
-            "      }"+
-            "      l.add(jmap);"+
-            "    }"+
-            "    listmapMap.put(key, l);"+
-            "  }  else {" +
-            "   org.openstreetmap.josm.data.CustomConfigurator.log('Unknown type:'+val.type+ '- use list, listlist or listmap'); }"+
-            "  }";
-        engine.eval(finish);
-
-        @SuppressWarnings("unchecked")
-        Map<String, String> stringMap = (Map<String, String>) engine.get("stringMap");
-        @SuppressWarnings("unchecked")
-        Map<String, List<String>> listMap = (Map<String, List<String>>) engine.get("listMap");
-        @SuppressWarnings("unchecked")
-        Map<String, List<Collection<String>>> listlistMap = (Map<String, List<Collection<String>>>) engine.get("listlistMap");
-        @SuppressWarnings("unchecked")
-        Map<String, List<Map<String, String>>> listmapMap = (Map<String, List<Map<String, String>>>) engine.get("listmapMap");
-
-        tmpPref.settingsMap.clear();
-
-        Map<String, Setting<?>> tmp = new HashMap<>();
-        for (Entry<String, String> e : stringMap.entrySet()) {
-            tmp.put(e.getKey(), new StringSetting(e.getValue()));
-        }
-        for (Entry<String, List<String>> e : listMap.entrySet()) {
-            tmp.put(e.getKey(), new ListSetting(e.getValue()));
-        }
-
-        for (Entry<String, List<Collection<String>>> e : listlistMap.entrySet()) {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            List<List<String>> value = (List) e.getValue();
-            tmp.put(e.getKey(), new ListListSetting(value));
-        }
-        for (Entry<String, List<Map<String, String>>> e : listmapMap.entrySet()) {
-            tmp.put(e.getKey(), new MapListSetting(e.getValue()));
-        }
-        for (Entry<String, Setting<?>> e : tmp.entrySet()) {
-            if (e.getValue().equals(tmpPref.defaultsMap.get(e.getKey()))) continue;
-            tmpPref.settingsMap.put(e.getKey(), e.getValue());
-        }
-    }
-
-    /**
-     * Convert preferences data structures to JavaScript object
-     * @param engine - JS engine to put object
-     * @param tmpPref - preferences to convert
-     * @param whereToPutInJS - variable name to store preferences in JS
-     * @param includeDefaults - include known default values to JS objects
-     * @throws ScriptException if the evaluation fails
-     */
-    public static void loadPrefsToJS(ScriptEngine engine, Preferences tmpPref, String whereToPutInJS, boolean includeDefaults)
-            throws ScriptException {
-        Map<String, String> stringMap = new TreeMap<>();
-        Map<String, List<String>> listMap = new TreeMap<>();
-        Map<String, List<List<String>>> listlistMap = new TreeMap<>();
-        Map<String, List<Map<String, String>>> listmapMap = new TreeMap<>();
-
-        if (includeDefaults) {
-            for (Map.Entry<String, Setting<?>> e: tmpPref.defaultsMap.entrySet()) {
-                Setting<?> setting = e.getValue();
-                if (setting instanceof StringSetting) {
-                    stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
-                } else if (setting instanceof ListSetting) {
-                    listMap.put(e.getKey(), ((ListSetting) setting).getValue());
-                } else if (setting instanceof ListListSetting) {
-                    listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
-                } else if (setting instanceof MapListSetting) {
-                    listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
-                }
-            }
-        }
-        tmpPref.settingsMap.entrySet().removeIf(e -> e.getValue().getValue() == null);
-
-        for (Map.Entry<String, Setting<?>> e: tmpPref.settingsMap.entrySet()) {
-            Setting<?> setting = e.getValue();
-            if (setting instanceof StringSetting) {
-                stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
-            } else if (setting instanceof ListSetting) {
-                listMap.put(e.getKey(), ((ListSetting) setting).getValue());
-            } else if (setting instanceof ListListSetting) {
-                listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
-            } else if (setting instanceof MapListSetting) {
-                listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
-            }
-        }
-
-        engine.put("stringMap", stringMap);
-        engine.put("listMap", listMap);
-        engine.put("listlistMap", listlistMap);
-        engine.put("listmapMap", listmapMap);
-
-        String init =
-            "function getJSList( javaList ) {"+
-            " var jsList; var i; "+
-            " if (javaList == null) return null;"+
-            "jsList = [];"+
-            "  for (i = 0; i < javaList.size(); i++) {"+
-            "    jsList.push(String(list.get(i)));"+
-            "  }"+
-            "return jsList;"+
-            "}"+
-            "function getJSMap( javaMap ) {"+
-            " var jsMap; var it; var e; "+
-            " if (javaMap == null) return null;"+
-            " jsMap = {};"+
-            " for (it = javaMap.entrySet().iterator(); it.hasNext();) {"+
-            "    e = it.next();"+
-            "    jsMap[ String(e.getKey()) ] = String(e.getValue()); "+
-            "  }"+
-            "  return jsMap;"+
-            "}"+
-            "for (it = stringMap.entrySet().iterator(); it.hasNext();) {"+
-            "  e = it.next();"+
-            whereToPutInJS+"[String(e.getKey())] = String(e.getValue());"+
-            "}\n"+
-            "for (it = listMap.entrySet().iterator(); it.hasNext();) {"+
-            "  e = it.next();"+
-            "  list = e.getValue();"+
-            "  jslist = getJSList(list);"+
-            "  jslist.type = 'list';"+
-            whereToPutInJS+"[String(e.getKey())] = jslist;"+
-            "}\n"+
-            "for (it = listlistMap.entrySet().iterator(); it.hasNext(); ) {"+
-            "  e = it.next();"+
-            "  listlist = e.getValue();"+
-            "  jslistlist = [];"+
-            "  for (it2 = listlist.iterator(); it2.hasNext(); ) {"+
-            "    list = it2.next(); "+
-            "    jslistlist.push(getJSList(list));"+
-            "    }"+
-            "  jslistlist.type = 'listlist';"+
-            whereToPutInJS+"[String(e.getKey())] = jslistlist;"+
-            "}\n"+
-            "for (it = listmapMap.entrySet().iterator(); it.hasNext();) {"+
-            "  e = it.next();"+
-            "  listmap = e.getValue();"+
-            "  jslistmap = [];"+
-            "  for (it2 = listmap.iterator(); it2.hasNext();) {"+
-            "    map = it2.next();"+
-            "    jslistmap.push(getJSMap(map));"+
-            "    }"+
-            "  jslistmap.type = 'listmap';"+
-            whereToPutInJS+"[String(e.getKey())] = jslistmap;"+
-            "}\n";
-
-            // Execute conversion script
-            engine.eval(init);
-        }
-    }
-}
Index: trunk/src/org/openstreetmap/josm/data/Preferences.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 12630)
+++ trunk/src/org/openstreetmap/josm/data/Preferences.java	(revision 12634)
@@ -235,4 +235,22 @@
 
     /**
+     * Constructs a new {@code Preferences}.
+     */
+    public Preferences() {
+        // Default constructor
+    }
+
+    /**
+     * Constructs a new {@code Preferences} from an existing instance.
+     * @param pref existing preferences to copy
+     * @since 12634
+     */
+    public Preferences(Preferences pref) {
+        settingsMap.putAll(pref.settingsMap);
+        defaultsMap.putAll(pref.defaultsMap);
+        colornames.putAll(pref.colornames);
+    }
+
+    /**
      * Adds a new preferences listener.
      * @param listener The listener to add
Index: trunk/src/org/openstreetmap/josm/data/PreferencesUtils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/PreferencesUtils.java	(revision 12634)
+++ trunk/src/org/openstreetmap/josm/data/PreferencesUtils.java	(revision 12634)
@@ -0,0 +1,430 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.preferences.ListListSetting;
+import org.openstreetmap.josm.data.preferences.ListSetting;
+import org.openstreetmap.josm.data.preferences.MapListSetting;
+import org.openstreetmap.josm.data.preferences.Setting;
+import org.openstreetmap.josm.data.preferences.StringSetting;
+import org.openstreetmap.josm.gui.io.CustomConfigurator;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Helper class to do specific Preferences operation - appending, replacing, deletion by key and by value
+ * Also contains functions that convert preferences object to JavaScript object and back
+ * @since 12634 (extracted from {@code CustomConfigurator})
+ */
+public final class PreferencesUtils {
+
+    private PreferencesUtils() {
+        // Hide implicit public constructor for utility class
+    }
+
+    public static void replacePreferences(Preferences fragment, Preferences mainpref) {
+        for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) {
+            mainpref.putSetting(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public static void appendPreferences(Preferences fragment, Preferences mainpref) {
+        for (Entry<String, Setting<?>> entry: fragment.settingsMap.entrySet()) {
+            String key = entry.getKey();
+            if (entry.getValue() instanceof StringSetting) {
+                mainpref.putSetting(key, entry.getValue());
+            } else if (entry.getValue() instanceof ListSetting) {
+                ListSetting lSetting = (ListSetting) entry.getValue();
+                Collection<String> newItems = getCollection(mainpref, key, true);
+                if (newItems == null) continue;
+                for (String item : lSetting.getValue()) {
+                    // add nonexisting elements to then list
+                    if (!newItems.contains(item)) {
+                        newItems.add(item);
+                    }
+                }
+                mainpref.putCollection(key, newItems);
+            } else if (entry.getValue() instanceof ListListSetting) {
+                ListListSetting llSetting = (ListListSetting) entry.getValue();
+                Collection<Collection<String>> newLists = getArray(mainpref, key, true);
+                if (newLists == null) continue;
+
+                for (Collection<String> list : llSetting.getValue()) {
+                    // add nonexisting list (equals comparison for lists is used implicitly)
+                    if (!newLists.contains(list)) {
+                        newLists.add(list);
+                    }
+                }
+                mainpref.putArray(key, newLists);
+            } else if (entry.getValue() instanceof MapListSetting) {
+                MapListSetting mlSetting = (MapListSetting) entry.getValue();
+                List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
+                if (newMaps == null) continue;
+
+                // get existing properties as list of maps
+
+                for (Map<String, String> map : mlSetting.getValue()) {
+                    // add nonexisting map (equals comparison for maps is used implicitly)
+                    if (!newMaps.contains(map)) {
+                        newMaps.add(map);
+                    }
+                }
+                mainpref.putListOfStructs(entry.getKey(), newMaps);
+            }
+        }
+    }
+
+    /**
+     * Delete items from {@code mainpref} collections that match items from {@code fragment} collections.
+     * @param fragment preferences
+     * @param mainpref main preferences
+     */
+    public static void deletePreferenceValues(Preferences fragment, Preferences mainpref) {
+
+        for (Entry<String, Setting<?>> entry : fragment.settingsMap.entrySet()) {
+            String key = entry.getKey();
+            if (entry.getValue() instanceof StringSetting) {
+                StringSetting sSetting = (StringSetting) entry.getValue();
+                // if mentioned value found, delete it
+                if (sSetting.equals(mainpref.settingsMap.get(key))) {
+                    mainpref.put(key, null);
+                }
+            } else if (entry.getValue() instanceof ListSetting) {
+                ListSetting lSetting = (ListSetting) entry.getValue();
+                Collection<String> newItems = getCollection(mainpref, key, true);
+                if (newItems == null) continue;
+
+                // remove mentioned items from collection
+                for (String item : lSetting.getValue()) {
+                    CustomConfigurator.log("Deleting preferences: from list %s: %s\n", key, item);
+                    newItems.remove(item);
+                }
+                mainpref.putCollection(entry.getKey(), newItems);
+            } else if (entry.getValue() instanceof ListListSetting) {
+                ListListSetting llSetting = (ListListSetting) entry.getValue();
+                Collection<Collection<String>> newLists = getArray(mainpref, key, true);
+                if (newLists == null) continue;
+
+                // if items are found in one of lists, remove that list!
+                Iterator<Collection<String>> listIterator = newLists.iterator();
+                while (listIterator.hasNext()) {
+                    Collection<String> list = listIterator.next();
+                    for (Collection<String> removeList : llSetting.getValue()) {
+                        if (list.containsAll(removeList)) {
+                            // remove current list, because it matches search criteria
+                            CustomConfigurator.log("Deleting preferences: list from lists %s: %s\n", key, list);
+                            listIterator.remove();
+                        }
+                    }
+                }
+
+                mainpref.putArray(key, newLists);
+            } else if (entry.getValue() instanceof MapListSetting) {
+                MapListSetting mlSetting = (MapListSetting) entry.getValue();
+                List<Map<String, String>> newMaps = getListOfStructs(mainpref, key, true);
+                if (newMaps == null) continue;
+
+                Iterator<Map<String, String>> mapIterator = newMaps.iterator();
+                while (mapIterator.hasNext()) {
+                    Map<String, String> map = mapIterator.next();
+                    for (Map<String, String> removeMap : mlSetting.getValue()) {
+                        if (map.entrySet().containsAll(removeMap.entrySet())) {
+                            // the map contain all mentioned key-value pair, so it should be deleted from "maps"
+                            CustomConfigurator.log("Deleting preferences: deleting map from maps %s: %s\n", key, map);
+                            mapIterator.remove();
+                        }
+                    }
+                }
+                mainpref.putListOfStructs(entry.getKey(), newMaps);
+            }
+        }
+    }
+
+    public static void deletePreferenceKeyByPattern(String pattern, Preferences pref) {
+        Map<String, Setting<?>> allSettings = pref.getAllSettings();
+        for (Entry<String, Setting<?>> entry : allSettings.entrySet()) {
+            String key = entry.getKey();
+            if (key.matches(pattern)) {
+                CustomConfigurator.log("Deleting preferences: deleting key from preferences: " + key);
+                pref.putSetting(key, null);
+            }
+        }
+    }
+
+    public static void deletePreferenceKey(String key, Preferences pref) {
+        Map<String, Setting<?>> allSettings = pref.getAllSettings();
+        if (allSettings.containsKey(key)) {
+            CustomConfigurator.log("Deleting preferences: deleting key from preferences: " + key);
+            pref.putSetting(key, null);
+        }
+    }
+
+    private static Collection<String> getCollection(Preferences mainpref, String key, boolean warnUnknownDefault) {
+        ListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListSetting.class);
+        ListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListSetting.class);
+        if (existing == null && defaults == null) {
+            if (warnUnknownDefault) defaultUnknownWarning(key);
+            return null;
+        }
+        if (existing != null)
+            return new ArrayList<>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
+    }
+
+    private static Collection<Collection<String>> getArray(Preferences mainpref, String key, boolean warnUnknownDefault) {
+        ListListSetting existing = Utils.cast(mainpref.settingsMap.get(key), ListListSetting.class);
+        ListListSetting defaults = Utils.cast(mainpref.defaultsMap.get(key), ListListSetting.class);
+
+        if (existing == null && defaults == null) {
+            if (warnUnknownDefault) defaultUnknownWarning(key);
+            return null;
+        }
+        if (existing != null)
+            return new ArrayList<>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
+    }
+
+    private static List<Map<String, String>> getListOfStructs(Preferences mainpref, String key, boolean warnUnknownDefault) {
+        MapListSetting existing = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
+        MapListSetting defaults = Utils.cast(mainpref.settingsMap.get(key), MapListSetting.class);
+
+        if (existing == null && defaults == null) {
+            if (warnUnknownDefault) defaultUnknownWarning(key);
+            return null;
+        }
+
+        if (existing != null)
+            return new ArrayList<>(existing.getValue());
+        else
+            return defaults.getValue() == null ? null : new ArrayList<>(defaults.getValue());
+    }
+
+    private static void defaultUnknownWarning(String key) {
+        CustomConfigurator.log("Warning: Unknown default value of %s , skipped\n", key);
+        JOptionPane.showMessageDialog(
+                Main.parent,
+                tr("<html>Settings file asks to append preferences to <b>{0}</b>,<br/> "+
+                        "but its default value is unknown at this moment.<br/> " +
+                        "Please activate corresponding function manually and retry importing.", key),
+                tr("Warning"),
+                JOptionPane.WARNING_MESSAGE);
+    }
+
+    public static void showPrefs(Preferences tmpPref) {
+        Logging.info("properties: " + tmpPref.settingsMap);
+    }
+
+    public static void modifyPreferencesByScript(ScriptEngine engine, Preferences tmpPref, String js) throws ScriptException {
+        loadPrefsToJS(engine, tmpPref, "API.pref", true);
+        engine.eval(js);
+        readPrefsFromJS(engine, tmpPref, "API.pref");
+    }
+
+    /**
+     * Convert JavaScript preferences object to preferences data structures
+     * @param engine - JS engine to put object
+     * @param tmpPref - preferences to fill from JS
+     * @param varInJS - JS variable name, where preferences are stored
+     * @throws ScriptException if the evaluation fails
+     */
+    public static void readPrefsFromJS(ScriptEngine engine, Preferences tmpPref, String varInJS) throws ScriptException {
+        String finish =
+            "stringMap = new java.util.TreeMap ;"+
+            "listMap =  new java.util.TreeMap ;"+
+            "listlistMap = new java.util.TreeMap ;"+
+            "listmapMap =  new java.util.TreeMap ;"+
+            "for (key in "+varInJS+") {"+
+            "  val = "+varInJS+"[key];"+
+            "  type = typeof val == 'string' ? 'string' : val.type;"+
+            "  if (type == 'string') {"+
+            "    stringMap.put(key, val);"+
+            "  } else if (type == 'list') {"+
+            "    l = new java.util.ArrayList;"+
+            "    for (i=0; i<val.length; i++) {"+
+            "      l.add(java.lang.String.valueOf(val[i]));"+
+            "    }"+
+            "    listMap.put(key, l);"+
+            "  } else if (type == 'listlist') {"+
+            "    l = new java.util.ArrayList;"+
+            "    for (i=0; i<val.length; i++) {"+
+            "      list=val[i];"+
+            "      jlist=new java.util.ArrayList;"+
+            "      for (j=0; j<list.length; j++) {"+
+            "         jlist.add(java.lang.String.valueOf(list[j]));"+
+            "      }"+
+            "      l.add(jlist);"+
+            "    }"+
+            "    listlistMap.put(key, l);"+
+            "  } else if (type == 'listmap') {"+
+            "    l = new java.util.ArrayList;"+
+            "    for (i=0; i<val.length; i++) {"+
+            "      map=val[i];"+
+            "      jmap=new java.util.TreeMap;"+
+            "      for (var key2 in map) {"+
+            "         jmap.put(key2,java.lang.String.valueOf(map[key2]));"+
+            "      }"+
+            "      l.add(jmap);"+
+            "    }"+
+            "    listmapMap.put(key, l);"+
+            "  }  else {" +
+            "   " + CustomConfigurator.class.getName() + ".log('Unknown type:'+val.type+ '- use list, listlist or listmap'); }"+
+            "  }";
+        engine.eval(finish);
+
+        @SuppressWarnings("unchecked")
+        Map<String, String> stringMap = (Map<String, String>) engine.get("stringMap");
+        @SuppressWarnings("unchecked")
+        Map<String, List<String>> listMap = (Map<String, List<String>>) engine.get("listMap");
+        @SuppressWarnings("unchecked")
+        Map<String, List<Collection<String>>> listlistMap = (Map<String, List<Collection<String>>>) engine.get("listlistMap");
+        @SuppressWarnings("unchecked")
+        Map<String, List<Map<String, String>>> listmapMap = (Map<String, List<Map<String, String>>>) engine.get("listmapMap");
+
+        tmpPref.settingsMap.clear();
+
+        Map<String, Setting<?>> tmp = new HashMap<>();
+        for (Entry<String, String> e : stringMap.entrySet()) {
+            tmp.put(e.getKey(), new StringSetting(e.getValue()));
+        }
+        for (Entry<String, List<String>> e : listMap.entrySet()) {
+            tmp.put(e.getKey(), new ListSetting(e.getValue()));
+        }
+
+        for (Entry<String, List<Collection<String>>> e : listlistMap.entrySet()) {
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            List<List<String>> value = (List) e.getValue();
+            tmp.put(e.getKey(), new ListListSetting(value));
+        }
+        for (Entry<String, List<Map<String, String>>> e : listmapMap.entrySet()) {
+            tmp.put(e.getKey(), new MapListSetting(e.getValue()));
+        }
+        for (Entry<String, Setting<?>> e : tmp.entrySet()) {
+            if (e.getValue().equals(tmpPref.defaultsMap.get(e.getKey()))) continue;
+            tmpPref.settingsMap.put(e.getKey(), e.getValue());
+        }
+    }
+
+    /**
+     * Convert preferences data structures to JavaScript object
+     * @param engine - JS engine to put object
+     * @param tmpPref - preferences to convert
+     * @param whereToPutInJS - variable name to store preferences in JS
+     * @param includeDefaults - include known default values to JS objects
+     * @throws ScriptException if the evaluation fails
+     */
+    public static void loadPrefsToJS(ScriptEngine engine, Preferences tmpPref, String whereToPutInJS, boolean includeDefaults)
+            throws ScriptException {
+        Map<String, String> stringMap = new TreeMap<>();
+        Map<String, List<String>> listMap = new TreeMap<>();
+        Map<String, List<List<String>>> listlistMap = new TreeMap<>();
+        Map<String, List<Map<String, String>>> listmapMap = new TreeMap<>();
+
+        if (includeDefaults) {
+            for (Map.Entry<String, Setting<?>> e: tmpPref.defaultsMap.entrySet()) {
+                Setting<?> setting = e.getValue();
+                if (setting instanceof StringSetting) {
+                    stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
+                } else if (setting instanceof ListSetting) {
+                    listMap.put(e.getKey(), ((ListSetting) setting).getValue());
+                } else if (setting instanceof ListListSetting) {
+                    listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
+                } else if (setting instanceof MapListSetting) {
+                    listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
+                }
+            }
+        }
+        tmpPref.settingsMap.entrySet().removeIf(e -> e.getValue().getValue() == null);
+
+        for (Map.Entry<String, Setting<?>> e: tmpPref.settingsMap.entrySet()) {
+            Setting<?> setting = e.getValue();
+            if (setting instanceof StringSetting) {
+                stringMap.put(e.getKey(), ((StringSetting) setting).getValue());
+            } else if (setting instanceof ListSetting) {
+                listMap.put(e.getKey(), ((ListSetting) setting).getValue());
+            } else if (setting instanceof ListListSetting) {
+                listlistMap.put(e.getKey(), ((ListListSetting) setting).getValue());
+            } else if (setting instanceof MapListSetting) {
+                listmapMap.put(e.getKey(), ((MapListSetting) setting).getValue());
+            }
+        }
+
+        engine.put("stringMap", stringMap);
+        engine.put("listMap", listMap);
+        engine.put("listlistMap", listlistMap);
+        engine.put("listmapMap", listmapMap);
+
+        String init =
+            "function getJSList( javaList ) {"+
+            " var jsList; var i; "+
+            " if (javaList == null) return null;"+
+            "jsList = [];"+
+            "  for (i = 0; i < javaList.size(); i++) {"+
+            "    jsList.push(String(list.get(i)));"+
+            "  }"+
+            "return jsList;"+
+            "}"+
+            "function getJSMap( javaMap ) {"+
+            " var jsMap; var it; var e; "+
+            " if (javaMap == null) return null;"+
+            " jsMap = {};"+
+            " for (it = javaMap.entrySet().iterator(); it.hasNext();) {"+
+            "    e = it.next();"+
+            "    jsMap[ String(e.getKey()) ] = String(e.getValue()); "+
+            "  }"+
+            "  return jsMap;"+
+            "}"+
+            "for (it = stringMap.entrySet().iterator(); it.hasNext();) {"+
+            "  e = it.next();"+
+            whereToPutInJS+"[String(e.getKey())] = String(e.getValue());"+
+            "}\n"+
+            "for (it = listMap.entrySet().iterator(); it.hasNext();) {"+
+            "  e = it.next();"+
+            "  list = e.getValue();"+
+            "  jslist = getJSList(list);"+
+            "  jslist.type = 'list';"+
+            whereToPutInJS+"[String(e.getKey())] = jslist;"+
+            "}\n"+
+            "for (it = listlistMap.entrySet().iterator(); it.hasNext(); ) {"+
+            "  e = it.next();"+
+            "  listlist = e.getValue();"+
+            "  jslistlist = [];"+
+            "  for (it2 = listlist.iterator(); it2.hasNext(); ) {"+
+            "    list = it2.next(); "+
+            "    jslistlist.push(getJSList(list));"+
+            "    }"+
+            "  jslistlist.type = 'listlist';"+
+            whereToPutInJS+"[String(e.getKey())] = jslistlist;"+
+            "}\n"+
+            "for (it = listmapMap.entrySet().iterator(); it.hasNext();) {"+
+            "  e = it.next();"+
+            "  listmap = e.getValue();"+
+            "  jslistmap = [];"+
+            "  for (it2 = listmap.iterator(); it2.hasNext();) {"+
+            "    map = it2.next();"+
+            "    jslistmap.push(getJSMap(map));"+
+            "    }"+
+            "  jslistmap.type = 'listmap';"+
+            whereToPutInJS+"[String(e.getKey())] = jslistmap;"+
+            "}\n";
+
+        // Execute conversion script
+        engine.eval(init);
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 12630)
+++ trunk/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 12634)
@@ -16,4 +16,5 @@
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.concurrent.ExecutorService;
 
 import org.openstreetmap.josm.Main;
@@ -33,4 +34,5 @@
 public class ImageryLayerInfo {
 
+    /** Unique instance */
     public static final ImageryLayerInfo instance = new ImageryLayerInfo();
     /** List of all usable layers */
@@ -61,8 +63,15 @@
     }
 
+    /**
+     * Constructs a new {@code ImageryLayerInfo} from an existing one.
+     * @param info info to copy
+     */
     public ImageryLayerInfo(ImageryLayerInfo info) {
         layers.addAll(info.layers);
     }
 
+    /**
+     * Clear the lists of layers.
+     */
     public void clear() {
         layers.clear();
@@ -88,5 +97,5 @@
             Collections.sort(layers);
         }
-        loadDefaults(false, true, fastFail);
+        loadDefaults(false, null, fastFail);
     }
 
@@ -95,18 +104,18 @@
      *
      * The data is downloaded from the JOSM website (or loaded from cache).
-     * Entries marked as "default" are added to the user selection, if not
-     * already present.
+     * Entries marked as "default" are added to the user selection, if not already present.
      *
      * @param clearCache if true, clear the cache and start a fresh download.
-     * @param quiet whether not the loading should be performed using a {@link PleaseWaitRunnable} in the background
+     * @param worker executor service which will perform the loading. If null, it should be performed using a {@link PleaseWaitRunnable} in the background
      * @param fastFail whether opening HTTP connections should fail fast, see {@link ImageryReader#setFastFail(boolean)}
-     */
-    public void loadDefaults(boolean clearCache, boolean quiet, boolean fastFail) {
+     * @since 12634
+     */
+    public void loadDefaults(boolean clearCache, ExecutorService worker, boolean fastFail) {
         final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail);
-        if (quiet) {
+        if (worker == null) {
             loader.realRun();
             loader.finish();
         } else {
-            Main.worker.execute(new DefaultEntryLoader(clearCache, fastFail));
+            worker.execute(loader);
         }
     }
@@ -327,12 +336,23 @@
     }
 
+    /**
+     * Add a new imagery entry.
+     * @param info imagery entry to add
+     */
     public void add(ImageryInfo info) {
         layers.add(info);
     }
 
+    /**
+     * Remove an imagery entry.
+     * @param info imagery entry to remove
+     */
     public void remove(ImageryInfo info) {
         layers.remove(info);
     }
 
+    /**
+     * Save the list of imagery entries to preferences.
+     */
     public void save() {
         List<ImageryPreferenceEntry> entries = new ArrayList<>();
