net.sourceforge.texlipse.properties.KeyValueListFieldEditor.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.texlipse.properties.KeyValueListFieldEditor.java

Source

/*
 * $Id$
 *
 * Copyright (c) 2004-2005 by the TeXlapse Team.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package net.sourceforge.texlipse.properties;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import net.sourceforge.texlipse.PathUtils;
import net.sourceforge.texlipse.TexlipsePlugin;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;

/**
 * A FieldEditor that holds a list of key-value-pairs as its value.
 * 
 * The list of strings is converted into a single String object by 
 * putting a separator character between the items while concatenating.
 * 
 * @author Kimmo Karlsson
 */
public class KeyValueListFieldEditor extends FieldEditor implements IInputValidator {

    // separator character for strings
    public static final String SEPARATOR = ",";

    // list of invalid characters that can't be in a keyword
    private static final String INVALID_CHARS = " ,=*()[]{}<>|\\?+/&#%$#@\"!";

    private static final String ENV_TABLE_KEY = "key";
    private static final String ENV_TABLE_VALUE = "value";

    // component holding environment variable table and label
    private Composite table;

    // environment variable table component
    private TableViewer environmentTable;

    // for adding new variables
    private Button envAddButton;

    // for removing variables
    private Button envRemoveButton;

    // for editing existing variables
    private Button envEditButton;

    // for importing variables from the shell environment
    private Button envImportButton;

    // the environment variables
    private Map environment;

    /**
     * Environment variable for the environment table.
     * @see org.eclipse.debug.internal.ui.launchConfigurations.EnvironmentVariable
     */
    class EnvironmentVariable {

        // The name of the environment variable
        private String name;

        // The value of the environment variable
        private String value;

        /**
         * Create new key/value -pair.
         * @param name key
         * @param value value
         */
        public EnvironmentVariable(String name, String value) {
            this.name = name;
            this.value = value;
        }

        /**
         * Returns this variable's name, which serves as the key in the key/value
         * pair this variable represents
         * 
         * @return this variable's name
         */
        public String getName() {
            return name;
        }

        /**
         * Returns this variables value.
         * @return this variable's value
         */
        public String getValue() {
            return value;
        }

        /**
         * Sets this variable's name (key)
         * @param name name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * Sets this variable's value
         * @param value value
         */
        public void setValue(String value) {
            this.value = value;
        }

        /**
         * Returns this variable's name.
         * @return this variable's name
         */
        public String toString() {
            return getName();
        }

        /**
         * Compare names.
         */
        public boolean equals(Object obj) {
            boolean equal = false;
            if (obj instanceof EnvironmentVariable) {
                EnvironmentVariable var = (EnvironmentVariable) obj;
                equal = var.getName().equals(name);
            }
            return equal;
        }

        /**
         * @return name.hashCode()
         */
        public int hashCode() {
            return name.hashCode();
        }
    }

    /**
     * Content provider for the environment table.
     * @see org.eclipse.debug.ui.EnvironmentTab.EnvironmentVariableContentProvider
     */
    class EnvironmentVariableContentProvider implements IStructuredContentProvider {
        public Object[] getElements(Object inputElement) {
            EnvironmentVariable[] elements = new EnvironmentVariable[0];
            Map m = (Map) inputElement;
            if (m != null && !m.isEmpty()) {
                elements = new EnvironmentVariable[m.size()];
                String[] varNames = new String[m.size()];
                m.keySet().toArray(varNames);
                for (int i = 0; i < m.size(); i++) {
                    elements[i] = new EnvironmentVariable(varNames[i], (String) m.get(varNames[i]));
                }
            }
            return elements;
        }

        public void dispose() {
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            if (newInput == null) {
                return;
            }
            if (viewer instanceof TableViewer) {
                TableViewer tableViewer = (TableViewer) viewer;
                if (tableViewer.getTable().isDisposed()) {
                    return;
                }
                tableViewer.setSorter(new ViewerSorter() {
                    public int compare(Viewer iviewer, Object e1, Object e2) {
                        if (e1 == null) {
                            return -1;
                        } else if (e2 == null) {
                            return 1;
                        } else {
                            return ((EnvironmentVariable) e1).getName()
                                    .compareToIgnoreCase(((EnvironmentVariable) e2).getName());
                        }
                    }
                });
            }
        }
    }

    /**
     * Label provider for the environment table.
     * @see org.eclipse.debug.ui.EnvironmentTab.EnvironmentVariableLabelProvider
     */
    class EnvironmentVariableLabelProvider extends LabelProvider implements ITableLabelProvider {
        public String getColumnText(Object element, int columnIndex) {
            String result = null;
            if (element != null) {
                EnvironmentVariable var = (EnvironmentVariable) element;
                switch (columnIndex) {
                case 0: // variable
                    result = var.getName();
                    break;
                case 1: // value
                    result = var.getValue();
                    break;
                }
            }
            return result;
        }

        public Image getColumnImage(Object element, int columnIndex) {
            return null;
        }
    }

    /**
     * Dialog to input variable name and value.
     */
    class KeyValueInputDialog extends MessageDialog {

        private IInputValidator validator;
        private Text keyTextField;
        protected String key;
        private Text valueTextField;
        protected String value;

        /**
         * Default constructor from super class. Do not use.
         * 
         * @param parentShell
         * @param dialogTitle
         * @param dialogTitleImage
         * @param dialogMessage
         * @param dialogImageType
         * @param dialogButtonLabels
         * @param defaultIndex
         */
        public KeyValueInputDialog(Shell parentShell, String dialogTitle, Image dialogTitleImage,
                String dialogMessage, int dialogImageType, String[] dialogButtonLabels, int defaultIndex) {
            super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels,
                    defaultIndex);
            key = "";
            value = "";
        }

        /**
         * Constructor with default values for many fields.
         * This is the preferred constructor.
         * 
         * @param shell parent shell
         * @param title dialog title
         * @param message dialog message
         * @param vali validator for the text
         */
        public KeyValueInputDialog(Shell shell, String title, String message, IInputValidator vali) {
            this(shell, title, null, message, QUESTION,
                    new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
            validator = vali;
        }

        /**
         * @return key
         */
        public String getKey() {
            return key;
        }

        /**
         * @param name key
         */
        public void setKey(String name) {
            key = name;
        }

        /**
         * @return value
         */
        public String getValue() {
            return value;
        }

        /**
         * @param val value
         */
        public void setValue(String val) {
            value = val;
        }

        /**
         * Creates and returns the contents of an area of the dialog which appears
         * below the message and above the button bar.
         * 
         * This implementation creates two labels and two textfields.
         * 
         * @param parent parent composite to contain the custom area
         * @return the custom area control, or <code>null</code>
         */
        protected Control createCustomArea(Composite parent) {

            // create the top level composite for the dialog area
            Composite composite = new Composite(parent, SWT.NULL);
            GridLayout layout = new GridLayout();
            layout.numColumns = 2;
            composite.setLayout(layout);
            composite.setLayoutData(new GridData(GridData.FILL_BOTH));

            Label keyLabel = new Label(composite, SWT.LEFT);
            keyLabel.setLayoutData(new GridData());
            keyLabel.setText("key:");

            keyTextField = new Text(composite, SWT.SINGLE | SWT.BORDER);
            keyTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            keyTextField.setText(key);
            keyTextField.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    if (validator != null) {
                        key = keyTextField.getText();
                        if (key != null) {
                            key = key.trim();
                        }
                        String error = validator.isValid(key);
                        Button ok = getButton(IDialogConstants.OK_ID);
                        ok.setEnabled(error == null);
                    } else {
                        key = keyTextField.getText();
                    }
                }
            });

            Label valueLabel = new Label(composite, SWT.LEFT);
            valueLabel.setLayoutData(new GridData());
            valueLabel.setText("value:");

            valueTextField = new Text(composite, SWT.SINGLE | SWT.BORDER);
            valueTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            valueTextField.setText(value);
            valueTextField.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    value = valueTextField.getText();
                }
            });

            return composite;
        }
    }

    /**
     * Dialog to input variables from the current environment.
     */
    class EnvVarInputDialog extends MessageDialog {

        private List envVarList;
        protected int[] selections;
        protected String[] items;

        /**
         * Default constructor from super class. Do not use.
         * 
         * @param parentShell
         * @param dialogTitle
         * @param dialogTitleImage
         * @param dialogMessage
         * @param dialogImageType
         * @param dialogButtonLabels
         * @param defaultIndex
         */
        public EnvVarInputDialog(Shell parentShell, String dialogTitle, Image dialogTitleImage,
                String dialogMessage, int dialogImageType, String[] dialogButtonLabels, int defaultIndex) {
            super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels,
                    defaultIndex);
            selections = new int[0];
            items = new String[0];
            setShellStyle(getShellStyle() | SWT.RESIZE);
        }

        /**
         * Constructor with default values for many fields.
         * This is the preferred way to construct this class.
         * 
         * @param shell parent shell
         * @param title dialog title
         * @param message dialog message
         * @param items items for the list
         */
        public EnvVarInputDialog(Shell shell, String title, String message, String[] items) {
            this(shell, title, null, message, QUESTION,
                    new String[] { IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL }, 0);
            this.items = items;
        }

        /**
         * Prevents the dialog from appearing too wide and narrow.
         */
        protected Point getInitialSize() {
            Point p = super.getInitialSize();

            int sum = 0;
            int maxLen = 0;
            for (int i = 0; i < items.length; i++) {
                int l = items[i].length();
                sum += l;
                if (l > maxLen) {
                    maxLen = l;
                }
            }
            sum /= items.length;

            FontData[] fd = envVarList.getFont().getFontData();
            p.x = (p.x - 100) * sum / maxLen + 100; // scale the list size down by the average length of an item
            p.y += fd[0].getHeight() * 8 * 2; // add 8 lines to the default height (*2 to get it right. don't know why this is needed)
            p.x *= 2; // don't know why this is needed
            return p;
        }

        /**
         * @return indexes of selected items
         */
        public int[] getSelections() {
            return selections;
        }

        /**
         * Creates and returns the contents of an area of the dialog which appears
         * below the message and above the button bar.
         * 
         * This implementation creates two labels and two textfields.
         * 
         * @param parent parent composite to contain the custom area
         * @return the custom area control, or <code>null</code>
         */
        protected Control createCustomArea(Composite parent) {

            Composite composite = new Composite(parent, SWT.NULL);
            composite.setLayoutData(new GridData(GridData.FILL_BOTH));
            composite.setLayout(new GridLayout());

            envVarList = new List(composite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
            envVarList.setLayoutData(new GridData(GridData.FILL_BOTH));
            envVarList.setItems(items);
            envVarList.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent event) {
                    selections = envVarList.getSelectionIndices();
                }
            });

            return composite;
        }
    }

    /**
     * Creates a new string list field editor.
     * 
     * @param name preference name
     * @param labelText text for description label
     * @param parent parent component
     */
    public KeyValueListFieldEditor(String name, String labelText, Composite parent) {
        super(name, labelText, parent);
        environment = new HashMap();
    }

    /**
     * Validates the input of the dialog.
     * @param newText the contents of the dialog's text field
     * @return error message, or null if text is valid
     */
    public String isValid(String newText) {
        boolean error = false;

        for (int i = 0; i < newText.length(); i++) {
            if (INVALID_CHARS.indexOf(newText.charAt(i)) >= 0) {
                error = true;
                break;
            }
        }

        return error ? "invalid character" : null;
    }

    /**
     * @return 2
     */
    public int getNumberOfControls() {
        return 2;
    }

    /**
     * 
     * @param numColumns number of columns in the page layout
     */
    protected void adjustForNumColumns(int numColumns) {
        ((GridData) table.getLayoutData()).horizontalSpan = numColumns - 1;
    }

    /**
     * @see org.eclipse.jface.preference.FieldEditor#doFillIntoGrid(org.eclipse.swt.widgets.Composite, int)
     * @param parent parent component
     * @param numColumns number of columns in the page layout
     */
    protected void doFillIntoGrid(Composite parent, int numColumns) {
        table = createTable(parent);
        ((GridData) table.getLayoutData()).horizontalSpan = numColumns - 1;
        createButtons(parent);
    }

    /**
     * @see org.eclipse.debug.ui.EnvironmentTab#createEnvironmentTable(org.eclipse.swt.widgets.Composite)
     * @param parent parent component
     * @return table container
     */
    protected Composite createTable(Composite parent) {
        Font font = parent.getFont();

        // Create table composite
        Composite tableComposite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        GridData gridData = new GridData(GridData.FILL_BOTH);
        gridData.heightHint = 150;
        tableComposite.setLayout(layout);
        tableComposite.setLayoutData(gridData);
        tableComposite.setFont(font);

        // Create label
        getLabelControl(tableComposite);

        // Create table
        environmentTable = new TableViewer(tableComposite,
                SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
        Table table = environmentTable.getTable();
        TableLayout tableLayout = new TableLayout();
        table.setLayout(tableLayout);
        table.setHeaderVisible(true);
        table.setFont(font);
        gridData = new GridData(GridData.FILL_BOTH);
        environmentTable.getControl().setLayoutData(gridData);
        environmentTable.setContentProvider(new EnvironmentVariableContentProvider());
        environmentTable.setLabelProvider(new EnvironmentVariableLabelProvider());
        environmentTable.setColumnProperties(new String[] { ENV_TABLE_KEY, ENV_TABLE_VALUE });
        environmentTable.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                handleTableSelectionChanged(event);
            }
        });
        environmentTable.addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                if (!environmentTable.getSelection().isEmpty()) {
                    handleEnvEditButtonSelected();
                }
            }
        });

        // Create columns
        ColumnWeightData columnLayout = new ColumnWeightData(50);
        tableLayout.addColumnData(columnLayout);
        TableColumn tc = new TableColumn(table, SWT.NONE, 0);
        tc.setResizable(columnLayout.resizable);
        tc.setText(TexlipsePlugin.getResourceString("preferenceKeyValueTableColumn1"));
        columnLayout = new ColumnWeightData(50);
        tableLayout.addColumnData(columnLayout);
        tc = new TableColumn(table, SWT.NONE, 1);
        tc.setResizable(columnLayout.resizable);
        tc.setText(TexlipsePlugin.getResourceString("preferenceKeyValueTableColumn2"));

        return tableComposite;
    }

    /**
     * 
     * @param parent parent component
     * @return button container
     */
    protected Composite createButtons(Composite parent) {

        Composite buttonComposite = new Composite(parent, SWT.NULL);
        buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        buttonComposite.setLayout(new GridLayout());

        Label empty = new Label(buttonComposite, SWT.NONE);
        empty.setLayoutData(new GridData());

        envAddButton = new Button(buttonComposite, SWT.PUSH);
        envAddButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueAddButton"));
        envAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        envAddButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                handleEnvAddButtonSelected();
            }
        });

        envRemoveButton = new Button(buttonComposite, SWT.PUSH);
        envRemoveButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueRemoveButton"));
        envRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        envRemoveButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                handleEnvRemoveButtonSelected();
            }
        });
        envRemoveButton.setEnabled(false);

        envEditButton = new Button(buttonComposite, SWT.PUSH);
        envEditButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueEditButton"));
        envEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        envEditButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                handleEnvEditButtonSelected();
            }
        });
        envEditButton.setEnabled(false);

        envImportButton = new Button(buttonComposite, SWT.PUSH);
        envImportButton.setText(TexlipsePlugin.getResourceString("preferenceKeyValueImportButton"));
        envImportButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        envImportButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                handleEnvImportButtonSelected();
            }
        });

        Label filler = new Label(buttonComposite, SWT.NONE);
        filler.setLayoutData(new GridData(GridData.FILL_VERTICAL));

        return buttonComposite;
    }

    /**
     * Handle add-button presses.
     */
    private void handleEnvAddButtonSelected() {

        KeyValueInputDialog dialog = new KeyValueInputDialog(getLabelControl().getShell(),
                TexlipsePlugin.getResourceString("preferenceKeyValueAddDialogTitle"),
                TexlipsePlugin.getResourceString("preferenceKeyValueAddDialogLabel"), this);

        int code = dialog.open();
        if (code == KeyValueInputDialog.OK) {

            String key = dialog.getKey();
            if (key == null || key.length() == 0) {
                return;
            }
            String value = dialog.getValue();

            environment.put(key, value);
            environmentTable.add(new EnvironmentVariable(key, value));
        }
    }

    /**
     * Handle remove-button presses.
     */
    private void handleEnvRemoveButtonSelected() {

        IStructuredSelection sel = (IStructuredSelection) environmentTable.getSelection();
        environmentTable.getControl().setRedraw(false);
        for (Iterator i = sel.iterator(); i.hasNext();) {
            EnvironmentVariable var = (EnvironmentVariable) i.next();
            environmentTable.remove(var);
            environment.remove(var.getName());
        }
        environmentTable.getControl().setRedraw(true);
    }

    /**
     * Handle edit-button presses.
     */
    private void handleEnvEditButtonSelected() {

        IStructuredSelection sel = (IStructuredSelection) environmentTable.getSelection();
        EnvironmentVariable var = (EnvironmentVariable) sel.iterator().next();
        if (var == null) {
            return;
        }

        KeyValueInputDialog dialog = new KeyValueInputDialog(getLabelControl().getShell(),
                TexlipsePlugin.getResourceString("preferenceKeyValueEditDialogTitle"),
                TexlipsePlugin.getResourceString("preferenceKeyValueEditDialogLabel"), this);

        dialog.setKey(var.getName());
        dialog.setValue(var.getValue());

        int code = dialog.open();
        if (code == KeyValueInputDialog.OK) {

            String key = dialog.getKey();
            if (key == null || key.length() == 0) {
                return;
            }
            String value = dialog.getValue();

            environmentTable.remove(var);
            environment.remove(var.getName());

            environment.put(key, value);
            environmentTable.add(new EnvironmentVariable(key, value));
        }
    }

    /**
     * Handle import-button presses.
     */
    private void handleEnvImportButtonSelected() {

        String[] items = PathUtils.getStrings(PathUtils.getEnv());

        EnvVarInputDialog dialog = new EnvVarInputDialog(getLabelControl().getShell(),
                TexlipsePlugin.getResourceString("preferenceEnvVarImportDialogTitle"),
                TexlipsePlugin.getResourceString("preferenceEnvVarImportDialogLabel"), items);

        int code = dialog.open();
        if (code == EnvVarInputDialog.OK) {

            int[] sel = dialog.getSelections();
            for (int i = 0; i < sel.length; i++) {

                String line = items[sel[i]];
                int index = line.indexOf('=');
                if (index > 0) {

                    String key = line.substring(0, index);
                    String value = line.substring(index + 1);

                    environment.put(key, value);
                    environmentTable.add(new EnvironmentVariable(key, value));
                }

            }
        }
    }

    /**
     * Responds to a selection changed event in the environment table
     * @param event the selection change event
     */
    protected void handleTableSelectionChanged(SelectionChangedEvent event) {
        int size = ((IStructuredSelection) event.getSelection()).size();
        envEditButton.setEnabled(size == 1);
        envRemoveButton.setEnabled(size > 0);
    }

    /**
     * Loads the environment variables from preference store.
     */
    protected void doLoad() {
        doLoadFrom(getPreferenceStore().getString(getPreferenceName()));
    }

    /**
     * Loads the environment variables from the given string.
     * @param str
     */
    protected void doLoadFrom(String str) {
        environment.clear();

        if (str == null) {
            return;
        }

        String[] binds = str.split(SEPARATOR);
        if (binds == null) {
            return;
        }

        for (int i = 0; i < binds.length; i++) {

            int index = binds[i].indexOf('=');
            if (index <= 0) {
                continue;
            }

            environment.put(binds[i].substring(0, index), binds[i].substring(index + 1));
        }

        environmentTable.setInput(environment);
    }

    /**
     * Loads the default environment variables from preference store.
     */
    protected void doLoadDefault() {
        doLoadFrom(getPreferenceStore().getDefaultString(getPreferenceName()));
    }

    /**
     * Stores the environment variables to single string.
     */
    protected void doStore() {
        StringBuffer sb = new StringBuffer();

        String[] keys = (String[]) environment.keySet().toArray(new String[0]);
        for (int i = 0; i < keys.length; i++) {

            sb.append(keys[i]);
            sb.append('=');
            sb.append(environment.get(keys[i]));
            if (i < keys.length - 1) {
                sb.append(',');
            }
        }

        getPreferenceStore().setValue(getPreferenceName(), sb.toString());
    }
}