ch.cyberduck.core.preferences.Preferences.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.preferences.Preferences.java

Source

package ch.cyberduck.core.preferences;

/*
 *  Copyright (c) 2005 David Kocher. All rights reserved.
 *  http://cyberduck.ch/
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  Bug fixes, suggestions and comments should be sent to:
 *  dkocher@cyberduck.ch
 */

import ch.cyberduck.core.DefaultProviderHelpService;
import ch.cyberduck.core.DisabledCertificateStore;
import ch.cyberduck.core.DisabledHostKeyCallback;
import ch.cyberduck.core.DisabledLocale;
import ch.cyberduck.core.DisabledLoginCallback;
import ch.cyberduck.core.DisabledPasswordCallback;
import ch.cyberduck.core.DisabledPasswordStore;
import ch.cyberduck.core.DisabledSleepPreventer;
import ch.cyberduck.core.DisabledTerminalService;
import ch.cyberduck.core.Host;
import ch.cyberduck.core.Local;
import ch.cyberduck.core.Scheme;
import ch.cyberduck.core.aquaticprime.DonationKeyFactory;
import ch.cyberduck.core.date.DefaultUserDateFormatter;
import ch.cyberduck.core.diagnostics.DefaultInetAddressReachability;
import ch.cyberduck.core.formatter.DecimalSizeFormatter;
import ch.cyberduck.core.io.watchservice.NIOEventWatchService;
import ch.cyberduck.core.local.DefaultLocalTouchFeature;
import ch.cyberduck.core.local.DefaultTemporaryFileService;
import ch.cyberduck.core.local.DefaultWorkingDirectoryFinder;
import ch.cyberduck.core.local.DisabledApplicationBadgeLabeler;
import ch.cyberduck.core.local.DisabledApplicationFinder;
import ch.cyberduck.core.local.DisabledApplicationLauncher;
import ch.cyberduck.core.local.DisabledBrowserLauncher;
import ch.cyberduck.core.local.DisabledIconService;
import ch.cyberduck.core.local.DisabledQuarantineService;
import ch.cyberduck.core.local.NativeLocalTrashFeature;
import ch.cyberduck.core.local.NullFileDescriptor;
import ch.cyberduck.core.local.NullLocalSymlinkFeature;
import ch.cyberduck.core.local.WorkingDirectoryFinderFactory;
import ch.cyberduck.core.notification.DisabledNotificationService;
import ch.cyberduck.core.proxy.DisabledProxyFinder;
import ch.cyberduck.core.random.DefaultSecureRandomProvider;
import ch.cyberduck.core.resources.DisabledIconCache;
import ch.cyberduck.core.serializer.impl.dd.HostPlistReader;
import ch.cyberduck.core.serializer.impl.dd.PlistDeserializer;
import ch.cyberduck.core.serializer.impl.dd.PlistSerializer;
import ch.cyberduck.core.serializer.impl.dd.PlistWriter;
import ch.cyberduck.core.serializer.impl.dd.ProfilePlistReader;
import ch.cyberduck.core.serializer.impl.dd.TransferPlistReader;
import ch.cyberduck.core.threading.DefaultThreadPool;
import ch.cyberduck.core.threading.DisabledActionOperationBatcher;
import ch.cyberduck.core.threading.DisabledAlertCallback;
import ch.cyberduck.core.transfer.DisabledTransferErrorCallback;
import ch.cyberduck.core.transfer.DisabledTransferPrompt;
import ch.cyberduck.core.transfer.Transfer;
import ch.cyberduck.core.transfer.TransferAction;
import ch.cyberduck.core.transfer.TransferStatus;
import ch.cyberduck.core.updater.DisabledPeriodicUpdater;
import ch.cyberduck.core.urlhandler.DisabledSchemeHandler;
import ch.cyberduck.core.vault.DisabledVault;
import ch.cyberduck.core.webloc.InternetShortcutFileWriter;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.io.File;
import java.net.URL;
import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;

/**
 * Holding all application preferences. Default values get overwritten when loading
 * the <code>PREFERENCES_FILE</code>.
 * Singleton class.
 */
public abstract class Preferences {
    private static final Logger log = Logger.getLogger(Preferences.class);

    protected final Map<String, String> defaults = new HashMap<String, String>();

    /*
      TTL for DNS queries
     */
    static {
        Security.setProperty("networkaddress.cache.ttl", "10");
        Security.setProperty("networkaddress.cache.negative.ttl", "5");
    }

    /**
     * Called after the defaults have been set.
     */
    protected void post() {
        // Ticket #2539
        if (this.getBoolean("connection.dns.ipv6")) {
            System.setProperty("java.net.preferIPv6Addresses", String.valueOf(true));
        }
    }

    /**
     * Update the given property with a string value.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public abstract void setProperty(final String property, String v);

    /**
     * Update the given property with a list value
     *
     * @param property The name of the property to create or update
     * @param values   The new or updated value
     */
    public abstract void setProperty(final String property, List<String> values);

    /**
     * Remove a user customized property from the preferences.
     *
     * @param property Property name
     */
    public abstract void deleteProperty(final String property);

    /**
     * Internally always saved as a string.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public void setProperty(final String property, final boolean v) {
        this.setProperty(property, v ? String.valueOf(true) : String.valueOf(false));
    }

    /**
     * Internally always saved as a string.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public void setProperty(final String property, final int v) {
        this.setProperty(property, String.valueOf(v));
    }

    /**
     * Internally always saved as a string.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public void setProperty(final String property, final float v) {
        this.setProperty(property, String.valueOf(v));
    }

    /**
     * Internally always saved as a string.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public void setProperty(final String property, final long v) {
        this.setProperty(property, String.valueOf(v));
    }

    /**
     * Internally always saved as a string.
     *
     * @param property The name of the property to create or update
     * @param v        The new or updated value
     */
    public void setProperty(final String property, final double v) {
        this.setProperty(property, String.valueOf(v));
    }

    private static final class Version {
        /**
         * @return The <code>Specification-Version</code> in the JAR manifest.
         */
        public static String getSpecification() {
            Package pkg = Version.class.getPackage();
            return (pkg == null) ? null : pkg.getSpecificationVersion();
        }

        /**
         * @return The <code>Implementation-Version</code> in the JAR manifest.
         */
        public static String getImplementation() {
            Package pkg = Version.class.getPackage();
            return (pkg == null) ? null : pkg.getImplementationVersion();
        }

        /**
         * A simple main method that prints the version and exits
         */
        public static void main(String[] args) {
            System.out.println("Version: " + getSpecification());
            System.out.println("Implementation: " + getImplementation());
        }
    }

    protected void setDefaults(final Properties properties) {
        for (Map.Entry<Object, Object> property : properties.entrySet()) {
            defaults.put(property.getKey().toString(), property.getValue().toString());
        }
    }

    /**
     * setting the default prefs values
     */
    protected void setDefaults() {
        defaults.put("application.version", Version.getSpecification());
        defaults.put("application.revision", Version.getImplementation());

        defaults.put("tmp.dir", System.getProperty("java.io.tmpdir"));

        /*
          How many times the application was launched
         */
        defaults.put("uses", "0");
        /*
          True if donation dialog will be displayed before quit
         */
        defaults.put("donate.reminder", String.valueOf(-1));
        defaults.put("donate.reminder.interval", String.valueOf(20)); // in days
        defaults.put("donate.reminder.date", String.valueOf(new Date(0).getTime()));

        defaults.put("defaulthandler.reminder", String.valueOf(true));

        defaults.put("mail.feedback", "mailto:support@cyberduck.io");

        defaults.put("website.donate", "https://cyberduck.io/donate/");
        defaults.put("website.home", "https://cyberduck.io/");
        defaults.put("website.help", "https://help.cyberduck.io/" + this.locale());
        defaults.put("website.bug", "https://trac.cyberduck.io/newticket?version={0}");
        defaults.put("website.crash", "https://crash.cyberduck.io/report");
        defaults.put("website.cli", "https://duck.sh/");
        defaults.put("website.license", "https://cyberduck.io/license");
        defaults.put("website.acknowledgments", "https://cyberduck.io/acknowledgments");

        defaults.put("rendezvous.enable", String.valueOf(true));
        defaults.put("rendezvous.loopback.suppress", String.valueOf(true));
        defaults.put("rendezvous.notification.limit", String.valueOf(0));

        defaults.put("growl.enable", String.valueOf(true));

        defaults.put("path.symboliclink.resolve", String.valueOf(false));
        /*
          Normalize path names
         */
        defaults.put("path.normalize", String.valueOf(true));
        defaults.put("path.normalize.unicode", String.valueOf(false));

        defaults.put("local.user.home", System.getProperty("user.home"));
        defaults.put("local.symboliclink.resolve", String.valueOf(false));
        defaults.put("local.normalize.prefix", String.valueOf(false));
        defaults.put("local.normalize.unicode", String.valueOf(true));
        defaults.put("local.normalize.tilde", String.valueOf(true));
        defaults.put("local.list.native", String.valueOf(true));
        defaults.put("local.delimiter", File.separator);
        defaults.put("local.temporaryfiles.shortening.threshold", String.valueOf(240));

        defaults.put("application.name", "Cyberduck");
        defaults.put("application.container.name", "duck");
        final String support = SupportDirectoryFinderFactory.get().find().getAbsolute();
        defaults.put("application.support.path", support);
        defaults.put("application.receipt.path", support);

        // Default bundled profiles location
        final Local resources = ApplicationResourcesFinderFactory.get().find();
        defaults.put("application.bookmarks.path", String.format("%s/bookmarks", resources.getAbsolute()));
        defaults.put("application.profiles.path", String.format("%s/profiles", resources.getAbsolute()));

        /*
          Lowercase folder name to use when looking for bookmarks in user support directory
         */
        defaults.put("bookmarks.folder.name", "Bookmarks");
        /*
          Lowercase folder name to use when looking for profiles in user support directory
         */
        defaults.put("profiles.folder.name", "Profiles");

        /*
          Maximum number of directory listings to cache using a most recently used implementation
         */
        defaults.put("browser.cache.size", String.valueOf(1000));
        defaults.put("transfer.cache.size", String.valueOf(100));
        defaults.put("icon.cache.size", String.valueOf(200));

        /*
          Caching NS* proxy instances.
         */
        defaults.put("browser.model.cache.size", String.valueOf(10000));

        /*
          Callback threshold
         */
        defaults.put("browser.list.limit.directory", String.valueOf(5000));
        defaults.put("browser.list.limit.container", String.valueOf(100));

        defaults.put("info.toolbar.selected", String.valueOf(0));
        defaults.put("preferences.toolbar.selected", String.valueOf(0));

        /*
          Current default browser view is outline view (0-List view, 1-Outline view, 2-Column view)
         */
        defaults.put("browser.view", "1");
        /*
          Save browser sessions when quitting and restore upon relaunch
         */
        defaults.put("browser.serialize", String.valueOf(true));

        defaults.put("browser.font.size", String.valueOf(12f));

        defaults.put("browser.view.autoexpand", String.valueOf(true));
        defaults.put("browser.view.autoexpand.useDelay", String.valueOf(true));
        defaults.put("browser.view.autoexpand.delay", "1.0"); // in seconds

        defaults.put("browser.hidden.regex", "\\..*");

        defaults.put("browser.open.untitled", String.valueOf(true));
        defaults.put("browser.open.bookmark.default", null);

        /*
          Confirm closing the browsing connection
         */
        defaults.put("browser.disconnect.confirm", String.valueOf(false));
        defaults.put("browser.disconnect.bookmarks.show", String.valueOf(false));

        /*
          Display only one info panel and change information according to selection in browser
         */
        defaults.put("browser.info.inspector", String.valueOf(true));

        defaults.put("browser.sort.ascending", String.valueOf(true));

        defaults.put("browser.alternatingRows", String.valueOf(false));
        defaults.put("browser.verticalLines", String.valueOf(false));
        defaults.put("browser.horizontalLines", String.valueOf(true));
        /*
          Show hidden files in browser by default
         */
        defaults.put("browser.showHidden", String.valueOf(false));
        defaults.put("browser.charset.encoding", "UTF-8");
        /*
          Edit double clicked files instead of downloading
         */
        defaults.put("browser.doubleclick.edit", String.valueOf(false));
        /*
          Rename files when return or enter key is pressed
         */
        defaults.put("browser.enterkey.rename", String.valueOf(true));

        /*
          Enable inline editing in browser
         */
        defaults.put("browser.editable", String.valueOf(true));

        /*
          Warn before renaming files
         */
        defaults.put("browser.move.confirm", String.valueOf(true));
        defaults.put("browser.copy.confirm", String.valueOf(false));

        defaults.put("browser.transcript.open", String.valueOf(false));
        defaults.put("browser.transcript.size.height", String.valueOf(200));

        /*
          Filename (Short Date Format)Extension
         */
        defaults.put("browser.duplicate.format", "{0} ({1}){2}");

        /*
          Use octal or decimal file sizes
         */
        defaults.put("browser.filesize.decimal", String.valueOf(false));
        defaults.put("browser.date.natural", String.valueOf(true));

        defaults.put("browser.delete.concurrency", String.valueOf(10));

        defaults.put("info.toggle.permission", String.valueOf(1));
        defaults.put("info.toggle.distribution", String.valueOf(0));
        defaults.put("info.toggle.s3", String.valueOf(0));

        defaults.put("connection.toggle.options", String.valueOf(0));
        defaults.put("bookmark.toggle.options", String.valueOf(0));

        defaults.put("alert.toggle.transcript", String.valueOf(0));

        defaults.put("transfer.toggle.details", String.valueOf(1));

        /*
          Default editor
         */
        defaults.put("editor.bundleIdentifier", "com.apple.TextEdit");
        defaults.put("editor.alwaysUseDefault", String.valueOf(false));

        defaults.put("editor.upload.permissions.change", String.valueOf(true));
        defaults.put("editor.upload.symboliclink.resolve", String.valueOf(true));

        /*
          Save bookmarks in ~/Library
         */
        defaults.put("favorites.save", String.valueOf(true));

        defaults.put("queue.removeItemWhenComplete", String.valueOf(false));
        /*
          The maximum number of concurrent transfers in transfer list
         */
        defaults.put("queue.maxtransfers", String.valueOf(2));
        /*
          Default transfer connection handling
         */
        defaults.put("queue.transfer.type.enabled",
                String.format("%s %s %s", String.valueOf(Host.TransferType.browser.name()),
                        String.valueOf(Host.TransferType.newconnection.name()),
                        String.valueOf(Host.TransferType.concurrent.name())));
        defaults.put("queue.transfer.type", String.valueOf(Host.TransferType.concurrent.name()));
        /*
          Warning when number of transfers in queue exceeds limit
         */
        defaults.put("queue.size.warn", String.valueOf(20));
        /*
          Bring transfer window to front
         */
        defaults.put("queue.window.open.default", String.valueOf(false));
        defaults.put("queue.window.open.transfer.start", String.valueOf(true));
        defaults.put("queue.window.open.transfer.stop", String.valueOf(false));

        /*
          Action when duplicate file exists
         */
        defaults.put("queue.download.action", TransferAction.callback.name());
        defaults.put("queue.upload.action", TransferAction.callback.name());
        defaults.put("queue.copy.action", TransferAction.callback.name());
        /*
          When triggered manually using 'Reload' in the Transfer window
         */
        defaults.put("queue.download.reload.action", TransferAction.callback.name());
        defaults.put("queue.upload.reload.action", TransferAction.callback.name());
        defaults.put("queue.copy.reload.action", TransferAction.callback.name());

        defaults.put("queue.upload.permissions.change", String.valueOf(false));
        defaults.put("queue.upload.permissions.default", String.valueOf(false));
        defaults.put("queue.upload.permissions.file.default", String.valueOf(644));
        defaults.put("queue.upload.permissions.folder.default", String.valueOf(755));

        defaults.put("queue.upload.timestamp.change", String.valueOf(false));
        /*
          Keep existing headers
         */
        defaults.put("queue.upload.file.metadata.change", String.valueOf(true));
        defaults.put("queue.upload.file.encryption.change", String.valueOf(true));
        defaults.put("queue.upload.file.redundancy.change", String.valueOf(true));

        defaults.put("queue.upload.checksum.calculate", String.valueOf(true));

        defaults.put("queue.upload.skip.enable", String.valueOf(true));
        defaults.put("queue.upload.skip.regex.default", ".*~\\..*|\\.DS_Store|\\.svn|CVS");
        defaults.put("queue.upload.skip.regex", ".*~\\..*|\\.DS_Store|\\.svn|CVS");

        defaults.put("queue.upload.priority.regex", "");

        /*
          Create temporary filename with an UUID and rename when upload is complete
         */
        defaults.put("queue.upload.file.temporary", String.valueOf(false));
        /*
          Format string for temporary filename. Default to filename-uuid
         */
        defaults.put("queue.upload.file.temporary.format", "{0}-{1}");

        defaults.put("queue.upload.file.rename.format", "{0} ({1}){2}");
        defaults.put("queue.download.file.rename.format", "{0} ({1}){2}");

        defaults.put("queue.download.permissions.change", String.valueOf(true));
        defaults.put("queue.download.permissions.default", String.valueOf(false));
        defaults.put("queue.download.permissions.file.default", String.valueOf(644));
        defaults.put("queue.download.permissions.folder.default", String.valueOf(755));

        defaults.put("queue.download.timestamp.change", String.valueOf(true));
        defaults.put("queue.download.checksum", String.valueOf(true));

        defaults.put("queue.download.skip.enable", String.valueOf(true));
        defaults.put("queue.download.skip.regex.default",
                ".*~\\..*|\\.DS_Store|\\.svn|CVS|RCS|SCCS|\\.git|\\.bzr|\\.bzrignore|\\.bzrtags|\\.hg|\\.hgignore|\\.hgtags|_darcs|\\.file-segments");
        defaults.put("queue.download.skip.regex",
                ".*~\\..*|\\.DS_Store|\\.svn|CVS|RCS|SCCS|\\.git|\\.bzr|\\.bzrignore|\\.bzrtags|\\.hg|\\.hgignore|\\.hgtags|_darcs|\\.file-segments");

        defaults.put("queue.download.priority.regex", "");

        defaults.put("queue.download.folder", WorkingDirectoryFinderFactory.get().find().getAbsolute());
        // Security scoped bookmark
        defaults.put("queue.download.folder.bookmark", null);

        defaults.put("queue.download.quarantine", String.valueOf(true));
        defaults.put("queue.download.wherefrom", String.valueOf(true));

        // Segmented concurrent downloads
        defaults.put("queue.download.segments", String.valueOf(false));
        defaults.put("queue.download.segments.threshold", String.valueOf(100L * 1024L * 1024L));
        defaults.put("queue.download.segments.size", String.valueOf(50L * 1024L * 1024L));

        /*
          Open completed downloads
         */
        defaults.put("queue.download.complete.open", String.valueOf(false));

        defaults.put("queue.dock.badge", String.valueOf(false));

        defaults.put("queue.sleep.prevent", String.valueOf(true));

        /*
          Bandwidth throttle options
         */
        {
            final StringBuilder options = new StringBuilder();
            options.append(5 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(10 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(20 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(50 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(100 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(150 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(200 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(500 * DecimalSizeFormatter.KILO.multiple()).append(",");
            options.append(1 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(2 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(5 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(10 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(15 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(20 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(50 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            options.append(100 * DecimalSizeFormatter.MEGA.multiple()).append(",");
            defaults.put("queue.bandwidth.options", options.toString());
        }
        /*
          Bandwidth throttle upload stream
         */
        defaults.put("queue.upload.bandwidth.bytes", String.valueOf(-1));
        /*
          Bandwidth throttle download stream
         */
        defaults.put("queue.download.bandwidth.bytes", String.valueOf(-1));

        /*
         * Concurrent connections
         */
        defaults.put("queue.connections.limit", String.valueOf(2));
        {
            final StringBuilder options = new StringBuilder();
            options.append(1).append(",");
            options.append(2).append(",");
            options.append(3).append(",");
            options.append(4).append(",");
            options.append(5).append(",");
            options.append(10).append(",");
            options.append(15).append(",");
            options.append(20).append(",");
            defaults.put("queue.connections.options", options.toString());
        }

        /*
          While downloading, update the icon of the downloaded file as a progress indicator
         */
        defaults.put("queue.download.icon.update", String.valueOf(true));
        defaults.put("queue.download.icon.threshold", String.valueOf(TransferStatus.MEGA * 5));

        /*
          Default synchronize action selected in the sync dialog
         */
        defaults.put("queue.prompt.sync.action.default", TransferAction.mirror.name());
        defaults.put("queue.prompt.download.action.default", TransferAction.overwrite.name());
        defaults.put("queue.prompt.upload.action.default", TransferAction.overwrite.name());
        defaults.put("queue.prompt.copy.action.default", TransferAction.overwrite.name());
        defaults.put("queue.prompt.move.action.default", TransferAction.overwrite.name());

        defaults.put("queue.transcript.open", String.valueOf(false));
        defaults.put("queue.transcript.size.height", String.valueOf(200));

        defaults.put("http.compression.enable", String.valueOf(true));

        /*
          HTTP routes to maximum number of connections allowed for those routes
         */
        defaults.put("http.connections.route", String.valueOf(10));
        defaults.put("http.connections.reuse", String.valueOf(true));
        /*
          Total number of connections in the pool
         */
        defaults.put("http.connections.total", String.valueOf(Integer.MAX_VALUE));
        defaults.put("http.connections.retry", String.valueOf(1));

        defaults.put("http.manager.timeout", String.valueOf(0)); // Infinite
        defaults.put("http.socket.buffer", String.valueOf(8192));
        defaults.put("http.credentials.charset", "ISO-8859-1");

        /*
          Enable or disable verification that the remote host taking part
          of a data connection is the same as the host to which the control
          connection is attached.
         */
        defaults.put("ftp.datachannel.verify", String.valueOf(false));
        defaults.put("ftp.socket.buffer", String.valueOf(0));

        defaults.put("ftp.parser.multiline.strict", String.valueOf(false));
        defaults.put("ftp.parser.reply.strict", String.valueOf(false));

        /*
          Send LIST -a
         */
        defaults.put("ftp.command.lista", String.valueOf(true));
        defaults.put("ftp.command.stat", String.valueOf(true));
        defaults.put("ftp.command.mlsd", String.valueOf(true));

        /*
          Fallback to active or passive mode respectively
         */
        defaults.put("ftp.connectmode.fallback", String.valueOf(false));
        /*
          Protect the data channel by default. For TLS, the data connection
          can have one of two security levels.
         1) Clear (requested by 'PROT C')
         2) Private (requested by 'PROT P')
         */
        defaults.put("ftp.tls.datachannel", "P"); //C
        defaults.put("ftp.tls.session.requirereuse", String.valueOf(true));
        defaults.put("ftp.ssl.session.cache.size", String.valueOf(100));

        /*
          Try to determine the timezone automatically using timestamp comparison from MLST and LIST
         */
        defaults.put("ftp.timezone.auto", String.valueOf(false));
        defaults.put("ftp.timezone.default", TimeZone.getDefault().getID());

        defaults.put("ftp.symlink.absolute", String.valueOf(false));

        /*
          Authentication header version
         */
        //defaults.put("s3.signature.version", "AWS2");
        defaults.put("s3.signature.version", "AWS4HMACSHA256");
        /*
          Default bucket location
         */
        defaults.put("s3.location", "us-east-1");
        defaults.put("s3.bucket.virtualhost.disable", String.valueOf(false));
        defaults.put("s3.bucket.requesterpays", String.valueOf(true));
        defaults.put("s3.domain", "amazonaws.com");
        defaults.put("s3.hostname.default", "s3.amazonaws.com");

        //defaults.put("s3.bucket.acl.default", "public-read");
        defaults.put("s3.bucket.acl.default", "private");

        /*
          Default redundancy level
         */
        defaults.put("s3.storage.class", "STANDARD");
        //defaults.put("s3.encryption.algorithm", "AES256");
        defaults.put("s3.encryption.algorithm", StringUtils.EMPTY);

        /*
          Validity for public S3 URLs
         */
        defaults.put("s3.url.expire.seconds", String.valueOf(24 * 60 * 60));

        defaults.put("s3.mfa.serialnumber", StringUtils.EMPTY);

        defaults.put("s3.listing.chunksize", String.valueOf(1000));

        defaults.put("s3.upload.md5", String.valueOf(true));

        defaults.put("s3.upload.multipart", String.valueOf(true));
        defaults.put("s3.upload.multipart.concurrency", String.valueOf(10));
        defaults.put("s3.upload.multipart.partsize.minimum", String.valueOf(5L * 1024L * 1024L));
        /*
          Threshold in bytes. Only use multipart uploads for files more than 100MB
         */
        defaults.put("s3.upload.multipart.threshold", String.valueOf(100L * 1024L * 1024L));
        defaults.put("s3.upload.multipart.required.threshold", String.valueOf(5L * 1024L * 1024L * 1024L));
        // Maximum number of parts is 10'000. With 10MB segements this gives a maximum object size of 100GB
        // Must be a multiple of org.cryptomator.cryptolib.v1.Constants.PAYLOAD_SIZE when using Cryptomator Vaults
        defaults.put("s3.upload.multipart.size", String.valueOf(10L * 1024L * 1024L)); // 10MB
        defaults.put("s3.copy.multipart.size", String.valueOf(5368709120L)); // 5GB

        defaults.put("s3.upload.expect-continue", String.valueOf(true));

        /*
          Transfer thresholds for qloudsonic.io
         */
        defaults.put("s3.download.udt.threshold", String.valueOf(Long.MAX_VALUE));
        defaults.put("s3.upload.udt.threshold", String.valueOf(Long.MAX_VALUE));

        defaults.put("s3.accelerate.prompt", String.valueOf(false));

        /*
          A prefix to apply to log file names
         */
        defaults.put("s3.logging.prefix", "logs/");
        defaults.put("google.logging.prefix", "log");
        defaults.put("cloudfront.logging.prefix", "logs/");

        defaults.put("onedrive.listing.chunksize", String.valueOf(1000));
        defaults.put("onedrive.upload.multipart.partsize.minimum", String.valueOf(320 * 1024));

        final int month = 60 * 60 * 24 * 30; //30 days in seconds
        defaults.put("s3.cache.seconds", String.valueOf(month));

        /*
          Default metadata for uploads. Format must be "key1=value1 key2=value2"
         */
        defaults.put("s3.metadata.default", StringUtils.EMPTY);

        defaults.put("s3.lifecycle.transition.options", "1 7 10 30 60 180 360 720");
        defaults.put("s3.lifecycle.delete.options", "1 7 10 30 60 180 360 720");

        defaults.put("s3.delete.multiple.partition", String.valueOf(1000));

        defaults.put("azure.metadata.default", StringUtils.EMPTY);
        defaults.put("azure.listing.chunksize", String.valueOf(1000));
        defaults.put("azure.upload.md5", String.valueOf(false));

        // Legacy authentication
        //        defaults.put("openstack.authentication.context", "/v1.0");
        // Keystone authentication
        defaults.put("openstack.authentication.context", "/v2.0/tokens");
        defaults.put("openstack.upload.metadata.md5", String.valueOf(false));
        defaults.put("openstack.metadata.default", StringUtils.EMPTY);
        defaults.put("openstack.list.container.limit", String.valueOf(100));
        defaults.put("openstack.list.object.limit", String.valueOf(10000));
        defaults.put("openstack.account.preload", String.valueOf(true));
        defaults.put("openstack.cdn.preload", String.valueOf(true));
        defaults.put("openstack.container.size.preload", String.valueOf(true));

        defaults.put("openstack.upload.md5", String.valueOf(true));

        defaults.put("openstack.upload.largeobject", String.valueOf(true));
        defaults.put("openstack.upload.largeobject.concurrency", String.valueOf(5));
        defaults.put("openstack.upload.largeobject.segments.prefix", ".file-segments/");
        defaults.put("openstack.upload.largeobject.threshold", String.valueOf(2L * 1024L * 1024L * 1024L)); // 2GB
        defaults.put("openstack.upload.largeobject.required.threshold", String.valueOf(5L * 1024L * 1024L * 1024L)); // 5GB
        defaults.put("openstack.upload.largeobject.size", String.valueOf(1000L * 1024L * 1024L)); // 1GB
        // Each segment, except for the final one, must be at least 1 megabyte
        defaults.put("openstack.upload.largeobject.size.minimum", String.valueOf(1 * 1024L * 1024L)); // 1MB
        // Remove segments when deleting large object manifest
        defaults.put("openstack.upload.largeobject.cleanup", String.valueOf(true));

        defaults.put("openstack.delete.multiple.partition", String.valueOf(10000));

        defaults.put("googledrive.list.limit", String.valueOf(1000));
        defaults.put("googledrive.teamdrive.enable", String.valueOf(true));

        defaults.put("b2.bucket.acl.default", "allPrivate");
        defaults.put("b2.listing.chunksize", String.valueOf(100));
        defaults.put("b2.upload.checksum.verify", String.valueOf(true));

        defaults.put("b2.upload.largeobject", String.valueOf(true));
        defaults.put("b2.upload.largeobject.concurrency", String.valueOf(5));
        defaults.put("b2.upload.largeobject.required.threshold", String.valueOf(5L * 1024L * 1024L * 1024L)); // 5GB
        // When uploading files larger than 200MB, use the large files support to break up the files into parts and upload the parts in parallel.
        defaults.put("b2.upload.largeobject.threshold", String.valueOf(200 * 1024L * 1024L)); // 200MB
        // Each part can be anywhere from 100MB to 5GB in size
        defaults.put("b2.upload.largeobject.size", String.valueOf(100 * 1024L * 1024L));
        defaults.put("b2.upload.largeobject.size.minimum", String.valueOf(5 * 1024L * 1024L));

        defaults.put("b2.metadata.default", StringUtils.EMPTY);

        defaults.put("sds.listing.chunksize", String.valueOf(500));
        defaults.put("sds.upload.multipart.chunksize", String.valueOf(0.5 * 1024L * 1024L));
        defaults.put("sds.encryption.missingkeys.scheduler.period", String.valueOf(120000)); // 2 minutes
        defaults.put("sds.encryption.keys.ttl", String.valueOf(60000)); // 1 minute

        /*
          NTLM Windows Domain
         */
        defaults.put("webdav.ntlm.domain", StringUtils.EMPTY);
        defaults.put("webdav.ntlm.workstation", StringUtils.EMPTY);

        /*
          Enable preemptive authentication if valid credentials are found
         */
        defaults.put("webdav.basic.preemptive", String.valueOf(true));

        /*
          Enable Expect-Continue handshake
         */
        defaults.put("webdav.expect-continue", String.valueOf(true));
        defaults.put("webdav.redirect.GET.follow", String.valueOf(true));
        defaults.put("webdav.redirect.HEAD.follow", String.valueOf(true));
        defaults.put("webdav.redirect.PUT.follow", String.valueOf(false));
        defaults.put("webdav.redirect.PROPFIND.follow", String.valueOf(true));

        defaults.put("webdav.upload.md5", String.valueOf(false));
        defaults.put("webdav.metadata.default", StringUtils.EMPTY);

        defaults.put("analytics.provider.qloudstat.setup", "https://qloudstat.com/configuration/add");
        defaults.put("analytics.provider.qloudstat.iam.policy", "{\n" + "    \"Statement\": [\n" + "        {\n"
                + "            \"Action\": [\n" + "                \"s3:GetObject\", \n"
                + "                \"s3:ListBucket\"\n" + "            ], \n" + "            \"Condition\": {\n"
                + "                \"Bool\": {\n" + "                    \"aws:SecureTransport\": \"true\"\n"
                + "                }\n" + "            }, \n" + "            \"Effect\": \"Allow\", \n"
                + "            \"Resource\": \"arn:aws:s3:::%s/*\"\n" + "        }, \n" + "        {\n"
                + "            \"Action\": [\n" + "                \"s3:ListAllMyBuckets\", \n"
                + "                \"s3:GetBucketLogging\", \n" + "                \"s3:GetBucketLocation\"\n"
                + "            ], \n" + "            \"Effect\": \"Allow\", \n"
                + "            \"Resource\": \"arn:aws:s3:::*\"\n" + "        }, \n" + "        {\n"
                + "            \"Action\": [\n" + "                \"cloudfront:GetDistribution\", \n"
                + "                \"cloudfront:GetDistributionConfig\", \n"
                + "                \"cloudfront:ListDistributions\", \n"
                + "                \"cloudfront:GetStreamingDistribution\", \n"
                + "                \"cloudfront:GetStreamingDistributionConfig\", \n"
                + "                \"cloudfront:ListStreamingDistributions\"\n" + "            ], \n"
                + "            \"Condition\": {\n" + "                \"Bool\": {\n"
                + "                    \"aws:SecureTransport\": \"true\"\n" + "                }\n"
                + "            }, \n" + "            \"Effect\": \"Allow\", \n"
                + "            \"Resource\": \"*\"\n" + "        }\n" + "    ]\n" + "}\n");

        /*
         * Session pool
         */
        defaults.put("connection.pool.minidle", String.valueOf(1));
        defaults.put("connection.pool.maxidle", String.valueOf(5));
        defaults.put("connection.pool.maxtotal", String.valueOf(Integer.MAX_VALUE));

        /*
          Default login name
         */
        defaults.put("connection.login.name", StringUtils.EMPTY);
        defaults.put("connection.login.anon.name", "anonymous");
        defaults.put("connection.login.anon.pass", "cyberduck@example.net");
        /*
          Search for passphrases in Keychain
         */
        defaults.put("connection.login.keychain", String.valueOf(true));
        /*
         * Save passwords for vaults in Keychain
         */
        defaults.put("vault.keychain", String.valueOf(false));

        defaults.put("connection.port.default", String.valueOf(21));
        defaults.put("connection.protocol.default", Scheme.ftp.name());

        /*
          SO_KEEPALIVE
         */
        defaults.put("connection.socket.keepalive", String.valueOf(true));
        /*
          SO_LINGER
         */
        defaults.put("connection.socket.linger", String.valueOf(false));
        /*
          Socket timeout
         */
        defaults.put("connection.timeout.seconds", String.valueOf(30));
        /*
          Retry to connect after a I/O failure automatically
         */
        defaults.put("connection.retry", String.valueOf(1));
        /*
          In seconds
         */
        defaults.put("connection.retry.delay", String.valueOf(0));
        defaults.put("connection.retry.backoff.enable", String.valueOf(false));

        defaults.put("connection.hostname.default", StringUtils.EMPTY);
        /*
          Convert hostname to Punycode
         */
        defaults.put("connection.hostname.idn", String.valueOf(true));

        /*
          java.net.preferIPv6Addresses
         */
        defaults.put("connection.dns.ipv6", String.valueOf(false));

        /*
          Read proxy settings from system preferences
         */
        defaults.put("connection.proxy.enable", String.valueOf(true));
        defaults.put("connection.proxy.ntlm.domain", StringUtils.EMPTY);

        /*
          Warning when opening connections sending credentials in plaintext
         */
        defaults.put(String.format("connection.unsecure.warning.%s", Scheme.ftp), String.valueOf(true));
        defaults.put(String.format("connection.unsecure.warning.%s", Scheme.http), String.valueOf(true));

        defaults.put("connection.ssl.provider.bouncycastle.position", String.valueOf(1));
        defaults.put("connection.ssl.protocols", "TLSv1.2,TLSv1.1,TLSv1");
        defaults.put("connection.ssl.cipher.blacklist", StringUtils.EMPTY);

        defaults.put("connection.ssl.x509.revocation.online", String.valueOf(false));

        defaults.put("connection.ssl.keystore.type", null);
        defaults.put("connection.ssl.keystore.provider", null);

        // Default secure random strong algorithm
        defaults.put("connection.ssl.securerandom.algorithm", "NativePRNG");
        defaults.put("connection.ssl.securerandom.provider", "SUN");

        /*
          Transfer read buffer size
         */
        defaults.put("connection.chunksize", String.valueOf(32768));
        /*
          Buffer size for wrapped buffered streams
         */
        defaults.put("connection.buffer", String.valueOf(8192));
        /*
          SO_SNDBUF
         */
        defaults.put("connection.buffer.send", String.valueOf(0));
        /*
          SO_RCVBUF
         */
        defaults.put("connection.buffer.receive", String.valueOf(0));

        defaults.put("disk.unmount.timeout", String.valueOf(2));

        /*
          Read favicon from Web URL
         */
        defaults.put("bookmark.favicon.download", String.valueOf(true));

        /*
          Default to large icon size
         */
        defaults.put("bookmark.icon.size", String.valueOf(64));
        defaults.put("bookmark.menu.icon.size", String.valueOf(16));

        /*
          Location of the openssh known_hosts file
         */
        defaults.put("ssh.knownhosts", "~/.ssh/known_hosts");
        defaults.put("ssh.knownhosts.hostname.hash", String.valueOf(false));
        defaults.put("ssh.knownhosts.bookmark", StringUtils.EMPTY);

        defaults.put("ssh.authentication.publickey.default.enable", String.valueOf(false));
        defaults.put("ssh.authentication.publickey.default.rsa", "~/.ssh/id_rsa");
        defaults.put("ssh.authentication.publickey.default.dsa", "~/.ssh/id_dsa");

        defaults.put("ssh.authentication.agent.enable", String.valueOf(true));

        defaults.put("ssh.heartbeat.provider", "keep-alive");
        defaults.put("ssh.heartbeat.seconds", String.valueOf(60));

        /*
          Enable ZLIB compression
         */
        defaults.put("ssh.compression", "zlib");

        defaults.put("ssh.algorithm.cipher.blacklist", StringUtils.EMPTY);
        defaults.put("ssh.algorithm.mac.blacklist", StringUtils.EMPTY);
        defaults.put("ssh.algorithm.kex.blacklist", StringUtils.EMPTY);
        defaults.put("ssh.algorithm.signature.blacklist", StringUtils.EMPTY);

        defaults.put("sftp.symlink.absolute", String.valueOf(false));

        defaults.put("sftp.read.maxunconfirmed", String.valueOf(64));
        defaults.put("sftp.write.maxunconfirmed", String.valueOf(64));

        defaults.put("archive.default", "tar.gz");

        /*
          Archiver
         */
        defaults.put("archive.command.create.tar", "cd {2}; tar -cpPf {0}.tar {1}");
        defaults.put("archive.command.create.tar.gz", "cd {2}; tar -czpPf {0}.tar.gz {1}");
        defaults.put("archive.command.create.tar.bz2", "cd {2}; tar -cjpPf {0}.tar.bz2 {1}");
        defaults.put("archive.command.create.zip", "cd {2}; zip -qr {0}.zip {1}");
        defaults.put("archive.command.create.gz", "gzip -qr {1}");
        defaults.put("archive.command.create.bz2", "bzip2 -zk {1}");

        /*
          Unarchiver
         */
        defaults.put("archive.command.expand.tar", "tar -xpPf {0} -C {1}");
        defaults.put("archive.command.expand.tar.gz", "tar -xzpPf {0} -C {1}");
        defaults.put("archive.command.expand.tar.bz2", "tar -xjpPf {0} -C {1}");
        defaults.put("archive.command.expand.zip", "unzip -qn {0} -d {1}");
        defaults.put("archive.command.expand.gz", "gzip -d {0}");
        defaults.put("archive.command.expand.bz2", "bzip2 -dk {0}");

        defaults.put("update.check", String.valueOf(true));
        final int day = 60 * 60 * 24;
        defaults.put("update.check.interval", String.valueOf(day)); // periodic update check in seconds
        // Last update check in milliseconds
        defaults.put("update.check.timestamp", String.valueOf(0));

        defaults.put("terminal.bundle.identifier", "com.apple.Terminal");
        defaults.put("terminal.command", "do script \"{0}\"");
        defaults.put("terminal.command.ssh", "ssh -t {0} {1}@{2} -p {3} \"cd {4} && exec \\$SHELL\"");

        defaults.put("network.interface.blacklist", StringUtils.EMPTY);

        defaults.put("threading.pool.size.max", String.valueOf(20));
        defaults.put("threading.pool.keepalive.seconds", String.valueOf(60L));

        defaults.put("cryptomator.enable", String.valueOf(true));
        defaults.put("cryptomator.vault.autodetect", String.valueOf(true));
    }

    protected void setLogging() {
        // Enable all logging levels to pass through bridge
        java.util.logging.Logger.getLogger("").setLevel(java.util.logging.Level.ALL);
        // Call only once during initialization time of your application
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();

        final URL configuration;
        final String file = defaults.get("logging.config");
        if (null == file) {
            configuration = Preferences.class.getClassLoader().getResource("log4j-default.xml");
        } else {
            configuration = Preferences.class.getClassLoader().getResource(file);
        }
        if (null != configuration) {
            DOMConfigurator.configure(configuration);
        } else {
            // Default if no logging configuration is found
            final Logger root = Logger.getRootLogger();
            root.setLevel(Level.ERROR);
        }
        if (StringUtils.isNotBlank(this.getProperty("logging"))) {
            // Allow to override default logging level
            final Logger root = Logger.getRootLogger();
            root.setLevel(Level.toLevel(this.getProperty("logging"), Level.ERROR));
        }
    }

    /**
     * Default value for a given property.
     *
     * @param property The property to query.
     * @return A default value if any or null if not found.
     */
    public String getDefault(final String property) {
        String value = defaults.get(property);
        if (null == value) {
            log.warn(String.format("No property with key '%s'", property));
        }
        return value;
    }

    /**
     * @param property The property to query.
     * @return The configured values determined by a whitespace separator.
     */
    public List<String> getList(final String property) {
        final String value = this.getProperty(property);
        if (StringUtils.isBlank(value)) {
            return Collections.emptyList();
        }
        return Arrays.asList(value.split("(?<!\\\\)\\p{javaWhitespace}+"));
    }

    public Map<String, String> getMap(final String property) {
        final List<String> list = this.getList(property);
        final Map<String, String> table = new HashMap<String, String>();
        for (String m : list) {
            if (StringUtils.isBlank(m)) {
                continue;
            }
            if (!m.contains("=")) {
                log.warn(String.format("Invalid header %s", m));
                continue;
            }
            int split = m.indexOf('=');
            String key = m.substring(0, split);
            if (StringUtils.isBlank(key)) {
                log.warn(String.format("Missing key in %s", m));
                continue;
            }
            String value = m.substring(split + 1);
            if (StringUtils.isEmpty(value)) {
                log.warn(String.format("Missing value in %s", m));
                continue;
            }
            table.put(key, value);
        }
        return table;
    }

    /**
     * Give value in user settings or default value if not customized.
     *
     * @param property The property to query.
     * @return The user configured value or default.
     */
    public abstract String getProperty(String property);

    public int getInteger(final String property) {
        final String v = this.getProperty(property);
        if (null == v) {
            return -1;
        }
        try {
            return Integer.parseInt(v);
        } catch (NumberFormatException e) {
            return (int) this.getDouble(property);
        }
    }

    public float getFloat(final String property) {
        final String v = this.getProperty(property);
        if (null == v) {
            return -1;
        }
        try {
            return Float.parseFloat(v);
        } catch (NumberFormatException e) {
            return (float) this.getDouble(property);
        }
    }

    public long getLong(final String property) {
        final String v = this.getProperty(property);
        if (null == v) {
            return -1;
        }
        try {
            return Long.parseLong(v);
        } catch (NumberFormatException e) {
            return (long) this.getDouble(property);
        }
    }

    public double getDouble(final String property) {
        final String v = this.getProperty(property);
        if (null == v) {
            return -1;
        }
        try {
            return Double.parseDouble(v);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    public boolean getBoolean(final String property) {
        final String v = this.getProperty(property);
        if (null == v) {
            return false;
        }
        if (v.equalsIgnoreCase(String.valueOf(true))) {
            return true;
        }
        if (v.equalsIgnoreCase(String.valueOf(false))) {
            return false;
        }
        if (v.equalsIgnoreCase(String.valueOf(1))) {
            return true;
        }
        if (v.equalsIgnoreCase(String.valueOf(0))) {
            return false;
        }
        return v.equalsIgnoreCase("yes");
    }

    protected void setFactories() {
        defaults.put("factory.serializer.class", PlistSerializer.class.getName());
        defaults.put("factory.deserializer.class", PlistDeserializer.class.getName());
        defaults.put("factory.reader.profile.class", ProfilePlistReader.class.getName());
        defaults.put("factory.writer.profile.class", PlistWriter.class.getName());
        defaults.put("factory.reader.transfer.class", TransferPlistReader.class.getName());
        defaults.put("factory.writer.transfer.class", PlistWriter.class.getName());
        defaults.put("factory.reader.host.class", HostPlistReader.class.getName());
        defaults.put("factory.writer.host.class", PlistWriter.class.getName());

        defaults.put("factory.locale.class", DisabledLocale.class.getName());
        defaults.put("factory.local.class", Local.class.getName());
        defaults.put("factory.certificatestore.class", DisabledCertificateStore.class.getName());
        defaults.put("factory.logincallback.class", DisabledLoginCallback.class.getName());
        defaults.put("factory.passwordcallback.class", DisabledPasswordCallback.class.getName());
        defaults.put("factory.alertcallback.class", DisabledAlertCallback.class.getName());
        defaults.put("factory.hostkeycallback.class", DisabledHostKeyCallback.class.getName());
        defaults.put("factory.transfererrorcallback.class", DisabledTransferErrorCallback.class.getName());
        defaults.put("factory.temporaryfiles.class", DefaultTemporaryFileService.class.getName());
        defaults.put("factory.touch.class", DefaultLocalTouchFeature.class.getName());
        defaults.put("factory.autorelease.class", DisabledActionOperationBatcher.class.getName());
        defaults.put("factory.schemehandler.class", DisabledSchemeHandler.class.getName());
        defaults.put("factory.iconservice.class", DisabledIconService.class.getName());
        defaults.put("factory.iconcache.class", DisabledIconCache.class.getName());
        defaults.put("factory.notification.class", DisabledNotificationService.class.getName());
        defaults.put("factory.sleeppreventer.class", DisabledSleepPreventer.class.getName());
        defaults.put("factory.quarantine.class", DisabledQuarantineService.class.getName());
        for (Transfer.Type t : Transfer.Type.values()) {
            defaults.put(String.format("factory.transferpromptcallback.%s.class", t.name()),
                    DisabledTransferPrompt.class.getName());
        }
        defaults.put("factory.supportdirectoryfinder.class", TemporarySupportDirectoryFinder.class.getName());
        defaults.put("factory.applicationresourcesfinder.class",
                TemporaryApplicationResourcesFinder.class.getName());
        defaults.put("factory.workingdirectory.class", DefaultWorkingDirectoryFinder.class.getName());
        defaults.put("factory.watchservice.class", NIOEventWatchService.class.getName());
        defaults.put("factory.proxy.class", DisabledProxyFinder.class.getName());
        defaults.put("factory.passwordstore.class", DisabledPasswordStore.class.getName());
        defaults.put("factory.dateformatter.class", DefaultUserDateFormatter.class.getName());
        defaults.put("factory.trash.class", NativeLocalTrashFeature.class.getName());
        defaults.put("factory.symlink.class", NullLocalSymlinkFeature.class.getName());
        defaults.put("factory.licensefactory.class", DonationKeyFactory.class.getName());
        defaults.put("factory.badgelabeler.class", DisabledApplicationBadgeLabeler.class.getName());
        defaults.put("factory.filedescriptor.class", NullFileDescriptor.class.getName());
        defaults.put("factory.terminalservice.class", DisabledTerminalService.class.getName());
        defaults.put("factory.applicationfinder.class", DisabledApplicationFinder.class.getName());
        defaults.put("factory.applicationlauncher.class", DisabledApplicationLauncher.class.getName());
        defaults.put("factory.browserlauncher.class", DisabledBrowserLauncher.class.getName());
        defaults.put("factory.reachability.class", DefaultInetAddressReachability.class.getName());
        defaults.put("factory.updater.class", DisabledPeriodicUpdater.class.getName());
        defaults.put("factory.threadpool.class", DefaultThreadPool.class.getName());
        defaults.put("factory.urlfilewriter.class", InternetShortcutFileWriter.class.getName());
        defaults.put("factory.vault.class", DisabledVault.class.getName());
        defaults.put("factory.securerandom.class", DefaultSecureRandomProvider.class.getName());
        defaults.put("factory.providerhelpservice.class", DefaultProviderHelpService.class.getName());
    }

    /**
     * Store preferences
     */
    public abstract void save();

    /**
     * Overriding the default values with preferences from the last session.
     */
    public abstract void load();

    /**
     * @return The preferred locale of all localizations available
     * in this application bundle
     */
    public String locale() {
        return this.applicationLocales().iterator().next();
    }

    /**
     * The localizations available in this application bundle
     * sorted by preference by the user.
     *
     * @return Available locales in application bundle
     */
    public abstract List<String> applicationLocales();

    /**
     * @return Available locales in system
     */
    public abstract List<String> systemLocales();

    /**
     * @param locale ISO Language identifier
     * @return Human readable language name in the target language
     */
    public String getDisplayName(final String locale) {
        java.util.Locale l;
        if (StringUtils.contains(locale, "_")) {
            l = new java.util.Locale(locale.split("_")[0], locale.split("_")[1]);
        } else {
            l = new java.util.Locale(locale);
        }
        return StringUtils.capitalize(l.getDisplayName(l));
    }
}