org.netbeans.modules.mongodb.ui.components.MongoURIEditorPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.netbeans.modules.mongodb.ui.components.MongoURIEditorPanel.java

Source

/* 
 * Copyright (C) 2015 Yann D'Isanto
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.netbeans.modules.mongodb.ui.components;

import com.mongodb.MongoClientURI;
import java.awt.event.KeyEvent;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.swing.DefaultListModel;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import org.netbeans.modules.mongodb.util.MongoUtil;
import org.openide.NotificationLineSupport;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;

/**
 *
 * @author Yann D'Isanto
 */
public final class MongoURIEditorPanel extends javax.swing.JPanel {

    private static final String URI_PREFIX = "mongodb://";

    private static final String UTF_8 = "UTF-8";

    private final ChangeSupport changeSupport = new ChangeSupport(this);

    private NotificationLineSupport notificationLineSupport;

    private final DefaultListModel<Host> hostsListModel = new DefaultListModel<>();

    private final DefaultListModel<Option> optionsListModel = new DefaultListModel<>();

    private final Object lock = new Object();

    private boolean uriUserInput = false;

    private boolean uriFieldUpdate = false;

    public MongoURIEditorPanel() {
        this(null);
    }

    public MongoURIEditorPanel(MongoClientURI uri) {
        initComponents();
        final DocumentListener hostValidationListener = new AbstractDocumentListener() {

            @Override
            protected void onChange(DocumentEvent e) {
                addHostButton.setEnabled(hostField.getText().isEmpty() == false);
            }
        };
        hostField.getDocument().addDocumentListener(hostValidationListener);
        final DocumentListener optionValidationListener = new AbstractDocumentListener() {

            @Override
            protected void onChange(DocumentEvent e) {
                final String optionName = optionNameField.getText();
                if (e.getDocument() == optionNameField.getDocument()) {
                    if (optionName.isEmpty() == false && MongoUtil.isSupportedOption(optionName) == false) {
                        warn("unsupported option");
                    } else {
                        updateURIField();
                    }
                }
                addOptionButton
                        .setEnabled(optionName.isEmpty() == false && optionValueField.getText().isEmpty() == false);
            }
        };
        optionNameField.getDocument().addDocumentListener(optionValidationListener);
        optionValueField.getDocument().addDocumentListener(optionValidationListener);
        final DocumentListener fireChangeDocumentListener = new AbstractDocumentListener() {

            @Override
            protected void onChange(DocumentEvent e) {
                updateURIField();
            }

        };
        usernameField.getDocument().addDocumentListener(fireChangeDocumentListener);
        passwordField.getDocument().addDocumentListener(fireChangeDocumentListener);
        databaseField.getDocument().addDocumentListener(fireChangeDocumentListener);
        uriField.getDocument().addDocumentListener(new AbstractDocumentListener() {

            @Override
            protected void onChange(DocumentEvent e) {
                synchronized (lock) {
                    if (uriUserInput || uriFieldUpdate) {
                        return;
                    }
                    uriUserInput = true;
                }
                try {
                    setMongoURI(new MongoClientURI(uriField.getText().trim()));
                } catch (IllegalArgumentException ex) {
                    error(ex.getLocalizedMessage());
                }
                uriUserInput = false;

            }
        });
        if (uri != null) {
            setMongoURI(uri);
        }
    }

    private void updateURIField() {
        synchronized (lock) {
            if (uriUserInput || uriFieldUpdate) {
                return;
            }
            uriFieldUpdate = true;
        }
        final String uri = computeMongoURIString();
        try {
            uriField.setText(new MongoClientURI(uri).getURI());
            clearNotificationLineSupport();
        } catch (IllegalArgumentException ex) {
            error(ex.getLocalizedMessage());
        }
        changeSupport.fireChange();
        uriFieldUpdate = false;
    }

    public boolean valid() {
        try {
            return new MongoClientURI(computeMongoURIString()) != null;
        } catch (IllegalArgumentException ex) {
            return false;
        }
    }

    public void setMongoURI(MongoClientURI uri) {
        hostsListModel.clear();
        optionsListModel.clear();
        for (String string : uri.getHosts()) {
            final String[] rawHost = string.split(":");
            final String hostname = urlDecode(rawHost[0]);
            final Integer port = rawHost.length > 1 ? Integer.parseInt(rawHost[1]) : null;
            hostsListModel.addElement(new Host(hostname, port));
        }
        usernameField.setText(uri.getUsername());
        if (uri.getPassword() != null) {
            passwordField.setText(new String(uri.getPassword()));
        }
        databaseField.setText(uri.getDatabase());
        final List<Option> options = decodeOptions(uri);
        optionsListModel.clear();
        for (Option option : options) {
            optionsListModel.addElement(option);
        }
        updateURIField();
    }

    public MongoClientURI getMongoURI() {
        return new MongoClientURI(uriField.getText().trim());
    }

    private String computeMongoURIString() {
        final StringBuilder sb = new StringBuilder(URI_PREFIX).append(encodeCredentials()).append(encodeHosts())
                .append(encodeDatabaseWithOptions());
        return sb.toString().trim();
    }

    private String encodeCredentials() {
        final String username = usernameField.getText();
        final String password = new String(passwordField.getPassword());
        if (username.isEmpty() && password.isEmpty()) {
            return "";
        }
        return new StringBuilder().append(urlEncode(username)).append(':').append(urlEncode(password)).append('@')
                .toString();
    }

    private String encodeHosts() {
        final StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Object item : hostsListModel.toArray()) {
            final Host host = (Host) item;
            if (first) {
                first = false;
            } else {
                sb.append(',');
            }
            sb.append(encodeHost(host));
        }
        return sb.toString();
    }

    private String encodeDatabaseWithOptions() {
        final StringBuilder sb = new StringBuilder().append(urlEncode(databaseField.getText()))
                .append(encodeOptions());
        if (sb.length() > 0) {
            sb.insert(0, "/");
        }
        return sb.toString();
    }

    private String encodeOptions() {
        if (optionsListModel.isEmpty()) {
            return "";
        }
        final StringBuilder sb = new StringBuilder("?");
        boolean first = true;
        for (Object item : optionsListModel.toArray()) {
            Option option = (Option) item;
            if (first) {
                first = false;
            } else {
                sb.append('&');
            }
            sb.append(urlEncode(option.name)).append('=').append(option.value);
        }
        return sb.toString();
    }

    private String encodeHost(Host host) {
        final StringBuilder sb = new StringBuilder(urlEncode(host.hostname));
        if (host.port != null) {
            sb.append(':').append(host.port);
        }
        return sb.toString();
    }

    private String urlDecode(String string) {
        if (string.isEmpty()) {
            return string;
        }
        try {
            return URLDecoder.decode(string, UTF_8);
        } catch (UnsupportedEncodingException ex) {
            Exceptions.printStackTrace(ex);
            throw new AssertionError();
        }

    }

    private String urlEncode(String string) {
        if (string.isEmpty()) {
            return string;
        }
        try {
            return URLEncoder.encode(string, UTF_8);
        } catch (UnsupportedEncodingException ex) {
            Exceptions.printStackTrace(ex);
            throw new AssertionError();
        }

    }

    private Host readHostFromUI() {
        final String hostname = hostField.getText();
        if (hostname.isEmpty()) {
            error("no hostname specified");
            hostField.requestFocusInWindow();
            return null;
        }
        final String port = portField.getText();
        return new Host(hostname, port.isEmpty() ? null : Integer.parseInt(port));
    }

    private List<Option> decodeOptions(MongoClientURI uri) {
        final List<Option> options = new ArrayList<>();
        final String uriString = uri.getURI();
        final int index = uriString.indexOf('?');
        if (index != -1) {
            final String optionsPart = uriString.substring(index + 1);
            for (String option : optionsPart.split("&|;")) {
                int idx = option.indexOf("=");
                if (idx >= 0) {
                    final String key = option.substring(0, idx);
                    final String value = option.substring(idx + 1);
                    options.add(new Option(key, value));
                }
            }
        }
        return options;
    }

    private Option readOptionFromUI() {
        final String optionName = optionNameField.getText();
        if (optionName.isEmpty()) {
            error("no option name specified");
            optionNameField.requestFocusInWindow();
            return null;
        }
        final String optionValue = optionValueField.getText();
        if (optionValue.isEmpty()) {
            error("no option value specified");
            optionValueField.requestFocusInWindow();
            return null;
        }
        return new Option(optionName, optionValue);
    }

    public NotificationLineSupport getNotificationLineSupport() {
        return notificationLineSupport;
    }

    public void setNotificationLineSupport(NotificationLineSupport notificationLineSupport) {
        this.notificationLineSupport = notificationLineSupport;
    }

    private void clearNotificationLineSupport() {
        if (notificationLineSupport != null) {
            notificationLineSupport.clearMessages();
        }
    }

    private void info(String message) {
        if (notificationLineSupport != null) {
            notificationLineSupport.setInformationMessage(message);
        }
    }

    private void error(String message) {
        System.out.println("error: " + message);
        if (notificationLineSupport != null) {
            System.out.println("nls: " + message);
            notificationLineSupport.setErrorMessage(message);
        }
    }

    private void warn(String message) {
        if (notificationLineSupport != null) {
            notificationLineSupport.setWarningMessage(message);
        }
    }

    public void addChangeListener(ChangeListener listener) {
        changeSupport.addChangeListener(listener);
    }

    public void removeChangeListener(ChangeListener listener) {
        changeSupport.removeChangeListener(listener);
    }

    private void focusFullSelectionOnTextComponent(JTextComponent component) {
        component.setSelectionStart(0);
        component.setSelectionEnd(component.getText().length());
        component.requestFocusInWindow();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        hostsPanel = new javax.swing.JPanel();
        hostField = new javax.swing.JTextField();
        hostLabel = new javax.swing.JLabel();
        addHostButton = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        hostsList = new javax.swing.JList<Host>();
        portField = new javax.swing.JTextField();
        portLabel = new javax.swing.JLabel();
        credentialsPanel = new javax.swing.JPanel();
        usernameLabel = new javax.swing.JLabel();
        passwordLabel = new javax.swing.JLabel();
        usernameField = new javax.swing.JTextField();
        passwordField = new javax.swing.JPasswordField();
        databasePanel = new javax.swing.JPanel();
        databaseLabel = new javax.swing.JLabel();
        databaseField = new javax.swing.JTextField();
        optionsLabel = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        optionsList = new javax.swing.JList<Option>();
        addOptionButton = new javax.swing.JButton();
        optionNameLabel = new javax.swing.JLabel();
        optionNameField = new javax.swing.JTextField();
        optionValueLabel = new javax.swing.JLabel();
        optionValueField = new javax.swing.JTextField();
        uriLabel = new javax.swing.JLabel();
        uriField = new javax.swing.JTextField();

        hostsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.hostsPanel.border.title"))); // NOI18N

        hostField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                hostFieldKeyPressed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(hostLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.hostLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(addHostButton, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.addHostButton.text")); // NOI18N
        addHostButton.setEnabled(false);
        addHostButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addHostButtonActionPerformed(evt);
            }
        });

        hostsList.setModel(hostsListModel);
        hostsList.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                hostsListMouseClicked(evt);
            }
        });
        hostsList.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                hostsListKeyPressed(evt);
            }
        });
        jScrollPane1.setViewportView(hostsList);

        portField.setDocument(new PortDocument());
        portField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                portFieldKeyPressed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(portLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.portLabel.text")); // NOI18N

        javax.swing.GroupLayout hostsPanelLayout = new javax.swing.GroupLayout(hostsPanel);
        hostsPanel.setLayout(hostsPanelLayout);
        hostsPanelLayout.setHorizontalGroup(hostsPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(hostsPanelLayout.createSequentialGroup().addContainerGap()
                        .addGroup(hostsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(jScrollPane1).addGroup(javax.swing.GroupLayout.Alignment.TRAILING,
                                        hostsPanelLayout.createSequentialGroup().addComponent(hostLabel)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(hostField).addGap(18, 18, 18).addComponent(portLabel)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(portField, javax.swing.GroupLayout.PREFERRED_SIZE, 92,
                                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                .addComponent(addHostButton)))
                        .addContainerGap()));
        hostsPanelLayout.setVerticalGroup(hostsPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(hostsPanelLayout.createSequentialGroup().addContainerGap()
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(hostsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(addHostButton).addComponent(hostLabel)
                                .addComponent(hostField, javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addComponent(portLabel).addComponent(portField,
                                        javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));

        credentialsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.credentialsPanel.border.title"))); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(usernameLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.usernameLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(passwordLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.passwordLabel.text")); // NOI18N

        javax.swing.GroupLayout credentialsPanelLayout = new javax.swing.GroupLayout(credentialsPanel);
        credentialsPanel.setLayout(credentialsPanelLayout);
        credentialsPanelLayout.setHorizontalGroup(credentialsPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(credentialsPanelLayout.createSequentialGroup().addContainerGap()
                        .addGroup(credentialsPanelLayout
                                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(credentialsPanelLayout.createSequentialGroup().addComponent(usernameLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(usernameField, javax.swing.GroupLayout.DEFAULT_SIZE, 375,
                                                Short.MAX_VALUE))
                                .addGroup(credentialsPanelLayout.createSequentialGroup().addComponent(passwordLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(passwordField)))
                        .addContainerGap()));

        credentialsPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL,
                new java.awt.Component[] { passwordLabel, usernameLabel });

        credentialsPanelLayout.setVerticalGroup(
                credentialsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(credentialsPanelLayout.createSequentialGroup().addContainerGap()
                                .addGroup(credentialsPanelLayout
                                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(usernameLabel).addComponent(usernameField,
                                                javax.swing.GroupLayout.PREFERRED_SIZE,
                                                javax.swing.GroupLayout.DEFAULT_SIZE,
                                                javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(credentialsPanelLayout
                                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(passwordLabel).addComponent(passwordField,
                                                javax.swing.GroupLayout.PREFERRED_SIZE,
                                                javax.swing.GroupLayout.DEFAULT_SIZE,
                                                javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));

        databasePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.databasePanel.border.title"))); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(databaseLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.databaseLabel.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(optionsLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.optionsLabel.text")); // NOI18N

        optionsList.setModel(optionsListModel);
        optionsList.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                optionsListMouseClicked(evt);
            }
        });
        optionsList.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                optionsListKeyPressed(evt);
            }
        });
        jScrollPane2.setViewportView(optionsList);

        org.openide.awt.Mnemonics.setLocalizedText(addOptionButton, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.addOptionButton.text")); // NOI18N
        addOptionButton.setEnabled(false);
        addOptionButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addOptionButtonActionPerformed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(optionNameLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.optionNameLabel.text")); // NOI18N

        optionNameField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                optionNameFieldKeyPressed(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(optionValueLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.optionValueLabel.text")); // NOI18N

        optionValueField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                optionValueFieldKeyPressed(evt);
            }
        });

        javax.swing.GroupLayout databasePanelLayout = new javax.swing.GroupLayout(databasePanel);
        databasePanel.setLayout(databasePanelLayout);
        databasePanelLayout.setHorizontalGroup(databasePanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(databasePanelLayout.createSequentialGroup().addContainerGap().addGroup(databasePanelLayout
                        .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(jScrollPane2)
                        .addGroup(databasePanelLayout.createSequentialGroup().addComponent(databaseLabel)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(databaseField))
                        .addGroup(databasePanelLayout.createSequentialGroup().addComponent(optionsLabel).addGap(0,
                                0, Short.MAX_VALUE))
                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING,
                                databasePanelLayout.createSequentialGroup().addComponent(optionNameLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(optionNameField, javax.swing.GroupLayout.PREFERRED_SIZE, 91,
                                                javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addGap(18, 18, 18).addComponent(optionValueLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(optionValueField)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(addOptionButton)))
                        .addContainerGap()));
        databasePanelLayout.setVerticalGroup(databasePanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(databasePanelLayout.createSequentialGroup().addContainerGap().addGroup(databasePanelLayout
                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE).addComponent(databaseLabel)
                        .addComponent(databaseField, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(optionsLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(databasePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(databasePanelLayout
                                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(optionNameLabel)
                                        .addComponent(optionNameField, javax.swing.GroupLayout.PREFERRED_SIZE,
                                                javax.swing.GroupLayout.DEFAULT_SIZE,
                                                javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addComponent(optionValueLabel).addComponent(optionValueField,
                                                javax.swing.GroupLayout.PREFERRED_SIZE,
                                                javax.swing.GroupLayout.DEFAULT_SIZE,
                                                javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addComponent(addOptionButton))
                        .addContainerGap()));

        org.openide.awt.Mnemonics.setLocalizedText(uriLabel, org.openide.util.NbBundle
                .getMessage(MongoURIEditorPanel.class, "MongoURIEditorPanel.uriLabel.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(hostsPanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(credentialsPanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(databasePanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addGroup(layout.createSequentialGroup().addComponent(uriLabel)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(uriField)))
                        .addContainerGap()));
        layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addContainerGap()
                        .addComponent(hostsPanel, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(credentialsPanel, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(databasePanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(uriLabel).addComponent(uriField,
                                        javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addContainerGap()));
    }// </editor-fold>//GEN-END:initComponents

    private void hostsListKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_hostsListKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_DELETE) {
            final int index = hostsList.getSelectedIndex();
            if (index != -1) {
                hostsListModel.remove(index);
                updateURIField();
            }
        }
    }//GEN-LAST:event_hostsListKeyPressed

    private void addHostButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addHostButtonActionPerformed
        final Host host = readHostFromUI();
        if (host != null) {
            if (hostsListModel.contains(host)) {
                hostsList.setSelectedValue(host, true);
            } else {
                hostsListModel.addElement(host);
                updateURIField();
                hostField.setText("");
                portField.setText("");
                hostField.requestFocusInWindow();
            }
        }
    }//GEN-LAST:event_addHostButtonActionPerformed

    private void addOptionButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addOptionButtonActionPerformed
        final Option option = readOptionFromUI();
        if (option != null) {
            if (optionsListModel.contains(option)) {
                optionsList.setSelectedValue(option, true);
            } else {
                optionsListModel.addElement(option);
                updateURIField();
                optionNameField.setText("");
                optionValueField.setText("");
                optionNameField.requestFocusInWindow();
            }
        }
    }//GEN-LAST:event_addOptionButtonActionPerformed

    private void optionsListKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_optionsListKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_DELETE) {
            final int index = optionsList.getSelectedIndex();
            if (index != -1) {
                optionsListModel.remove(index);
                updateURIField();
            }
        }
    }//GEN-LAST:event_optionsListKeyPressed

    private void optionNameFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_optionNameFieldKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            focusFullSelectionOnTextComponent(optionValueField);
            evt.consume();
        }
    }//GEN-LAST:event_optionNameFieldKeyPressed

    private void hostFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_hostFieldKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            focusFullSelectionOnTextComponent(portField);
            evt.consume();
        }
    }//GEN-LAST:event_hostFieldKeyPressed

    private void portFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_portFieldKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            if (addHostButton.isEnabled()) {
                addHostButtonActionPerformed(null);
            }
            evt.consume();
        }
    }//GEN-LAST:event_portFieldKeyPressed

    private void optionValueFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_optionValueFieldKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            if (addOptionButton.isEnabled()) {
                addOptionButtonActionPerformed(null);
            }
            evt.consume();
        }
    }//GEN-LAST:event_optionValueFieldKeyPressed

    private void hostsListMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_hostsListMouseClicked
        final int index = hostsList.getSelectedIndex();
        if (evt.getClickCount() == 2 && index != -1) {
            final Host host = hostsListModel.remove(index);
            updateURIField();
            hostField.setText(host.hostname);
            portField.setText(host.port != null ? host.port.toString() : "");
            focusFullSelectionOnTextComponent(hostField);
        }
    }//GEN-LAST:event_hostsListMouseClicked

    private void optionsListMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_optionsListMouseClicked
        final int index = optionsList.getSelectedIndex();
        if (evt.getClickCount() == 2 && index != -1) {
            final Option option = optionsListModel.remove(index);
            updateURIField();
            optionNameField.setText(option.name);
            optionValueField.setText(option.value);
            focusFullSelectionOnTextComponent(optionNameField);
        }
    }//GEN-LAST:event_optionsListMouseClicked

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addHostButton;
    private javax.swing.JButton addOptionButton;
    private javax.swing.JPanel credentialsPanel;
    private javax.swing.JTextField databaseField;
    private javax.swing.JLabel databaseLabel;
    private javax.swing.JPanel databasePanel;
    private javax.swing.JTextField hostField;
    private javax.swing.JLabel hostLabel;
    private javax.swing.JList<Host> hostsList;
    private javax.swing.JPanel hostsPanel;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JTextField optionNameField;
    private javax.swing.JLabel optionNameLabel;
    private javax.swing.JTextField optionValueField;
    private javax.swing.JLabel optionValueLabel;
    private javax.swing.JLabel optionsLabel;
    private javax.swing.JList<Option> optionsList;
    private javax.swing.JPasswordField passwordField;
    private javax.swing.JLabel passwordLabel;
    private javax.swing.JTextField portField;
    private javax.swing.JLabel portLabel;
    private javax.swing.JTextField uriField;
    private javax.swing.JLabel uriLabel;
    private javax.swing.JTextField usernameField;
    private javax.swing.JLabel usernameLabel;
    // End of variables declaration//GEN-END:variables

    private final class PortDocument extends PlainDocument {

        @Override
        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
            boolean valid = true;
            for (char c : str.toCharArray()) {
                if (c < '0' || c > '9') {
                    valid = false;
                    break;
                }
            }
            if (valid && str.length() > 0 && getLength() > 0) {
                final StringBuilder sb = new StringBuilder(getText(0, getLength()));
                sb.insert(offs, str);
                final String value = sb.toString();
                if (value.isEmpty() == false) {
                    try {
                        int port = Integer.parseInt(value);
                        valid = port > 0 && port < 65536;
                    } catch (NumberFormatException ex) {
                        // should never happens
                        Exceptions.printStackTrace(ex);
                        valid = false;
                    }
                }
            }
            if (valid) {
                super.insertString(offs, str, a);
            }
        }

    }

    private static final class Host {

        private final String hostname;

        private final Integer port;

        public Host(String hostname, Integer port) {
            this.hostname = hostname;
            this.port = port;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 17 * hash + Objects.hashCode(this.hostname);
            hash = 17 * hash + Objects.hashCode(this.port);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Host other = (Host) obj;
            if (!Objects.equals(this.hostname, other.hostname)) {
                return false;
            }
            return Objects.equals(this.port, other.port);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder(hostname);
            if (port != null) {
                sb.append(':').append(port);
            }
            return sb.toString();
        }
    }

    private static final class Option {

        private final String name;

        private final String value;

        public Option(String name, Object value) {
            this(name, String.valueOf(value));
        }

        public Option(String name, String value) {
            this.name = name;
            this.value = value;
        }

        @Override
        public String toString() {
            return new StringBuilder(name).append('=').append(value).toString();
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 83 * hash + Objects.hashCode(this.name);
            hash = 83 * hash + Objects.hashCode(this.value);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Option other = (Option) obj;
            if (!Objects.equals(this.name, other.name)) {
                return false;
            }
            return Objects.equals(this.value, other.value);
        }

    }

    private static abstract class AbstractDocumentListener implements DocumentListener {

        protected abstract void onChange(DocumentEvent e);

        @Override
        public void insertUpdate(DocumentEvent e) {
            onChange(e);
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            onChange(e);
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            onChange(e);
        }

    }

}