de.walware.statet.r.internal.console.ui.launching.RRemoteConsoleSelectionDialog.java Source code

Java tutorial

Introduction

Here is the source code for de.walware.statet.r.internal.console.ui.launching.RRemoteConsoleSelectionDialog.java

Source

/*=============================================================================#
 # Copyright (c) 2009-2015 Stephan Wahlbrink (WalWare.de) and others.
 # 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
 # 
 # Contributors:
 #     Stephan Wahlbrink - initial API and implementation
 #=============================================================================*/

package de.walware.statet.r.internal.console.ui.launching;

import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ibm.icu.text.DateFormat;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.dialogs.SelectionStatusDialog;
import org.eclipse.ui.statushandlers.StatusManager;

import de.walware.ecommons.ui.SharedUIResources;
import de.walware.ecommons.ui.util.DialogUtil;
import de.walware.ecommons.ui.util.LayoutUtil;
import de.walware.ecommons.ui.util.ViewerUtil.TreeComposite;

import de.walware.rj.server.RjsComConfig;
import de.walware.rj.server.Server;
import de.walware.rj.server.ServerInfo;

import de.walware.statet.r.internal.console.ui.RConsoleMessages;
import de.walware.statet.r.internal.console.ui.RConsoleUIPlugin;

public class RRemoteConsoleSelectionDialog extends SelectionStatusDialog {

    public static abstract class SpecialAddress {

        private final String fPublicHost;
        private final String fPrivateHost;
        private final int fPort;

        public SpecialAddress(final String publicHost, final String privateHost, final int port) {
            fPublicHost = publicHost;
            fPrivateHost = privateHost;
            fPort = port;
        }

        public abstract RMIClientSocketFactory getSocketFactory(IProgressMonitor monitor) throws CoreException;

    }

    private static final String SETTINGS_DIALOG_ID = "RRemoteConsoleSelection"; //$NON-NLS-1$
    private static final String SETTINGS_HOST_HISTORY_KEY = "hosts.history"; //$NON-NLS-1$

    private static final Pattern ADDRESS_MULTI_PATTERN = Pattern.compile("\\/?\\s*[\\,\\;]+\\s*"); //$NON-NLS-1$
    private static final Pattern ADDRESS_WITH_PORT_PATTERN = Pattern.compile("(.*):(\\d{1,5})"); //$NON-NLS-1$

    private static class RemoteR {

        final String hostName;
        final String hostIP;
        final String address;

        final ServerInfo info;

        RemoteR(final String hostName, final String hostIP, final String address, final ServerInfo info) {
            this.hostName = hostName;
            this.hostIP = hostIP;
            this.address = address;

            this.info = info;
        }

        String createSummary() {
            final StringBuilder sb = new StringBuilder(100);
            sb.append("Address:   ").append(address).append('\n');
            sb.append('\n');
            sb.append("Host-Name: ").append(hostName).append('\n');
            sb.append("Host-IP:   ").append(hostIP).append('\n');
            sb.append("Date:      ")
                    .append((info.getTimestamp() != 0) ? DateFormat.getDateInstance().format(info.getTimestamp())
                            : "<unknown>")
                    .append('\n');
            sb.append("Directory: ").append(info.getDirectory()).append('\n');
            sb.append("Status:    ");
            switch (this.info.getState()) {
            case Server.S_NOT_STARTED:
                sb.append("New  Ready to connect and start R");
                break;
            case Server.S_CONNECTED:
            case Server.S_CONNECTED_STALE:
                sb.append("Running  Connected (username is ");
                sb.append((info.getUsername(ServerInfo.USER_CONSOLE) != null)
                        ? info.getUsername(ServerInfo.USER_CONSOLE)
                        : "<unknown>").append(')');
                break;
            case Server.S_LOST:
                sb.append("Running  Connection lost / Ready to reconnect");
                break;
            case Server.S_DISCONNECTED:
                sb.append("Running  Disconnected / Ready to reconnect");
                break;
            case Server.S_STOPPED:
                sb.append("Stopped");
                break;
            default:
                sb.append("Unknown");
                break;
            }
            return sb.toString();
        }

    }

    private static class RemoteRContentProvider implements ITreeContentProvider {

        private final HashMap<String, RemoteR[]> fMapping = new HashMap<String, RemoteR[]>();

        @Override
        public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
        }

        @Override
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public Object[] getElements(final Object inputElement) {
            final List<RemoteR> all = (List<RemoteR>) inputElement;

            fMapping.clear();
            final Map mapping = fMapping;
            for (final RemoteR r : all) {
                final String username = r.info.getUsername(ServerInfo.USER_OWNER).toLowerCase();
                List<RemoteR> list = (List<RemoteR>) mapping.get(username);
                if (list == null) {
                    list = new ArrayList<RemoteR>();
                    mapping.put(username, list);
                }
                list.add(r);
            }

            final Set<Map.Entry> entrySet = mapping.entrySet();
            for (final Entry cat : entrySet) {
                final List<RemoteR> list = (List<RemoteR>) cat.getValue();
                cat.setValue(list.toArray(new RemoteR[list.size()]));
            }
            return fMapping.keySet().toArray();
        }

        @Override
        public Object getParent(final Object element) {
            if (element instanceof RemoteR) {
                return ((RemoteR) element).info.getUsername(ServerInfo.USER_OWNER);
            }
            return null;
        }

        @Override
        public boolean hasChildren(final Object element) {
            return (element instanceof String);
        }

        @Override
        public Object[] getChildren(final Object parentElement) {
            if (parentElement instanceof String) {
                return fMapping.get(parentElement);
            }
            return null;
        }

        @Override
        public void dispose() {
        }

    }

    private static abstract class RemoteRLabelProvider extends CellLabelProvider {

        @Override
        public void update(final ViewerCell cell) {
            final Object element = cell.getElement();
            String text = null;
            if (element instanceof RemoteR) {
                text = getText((RemoteR) element);
            }
            cell.setText(text);
        }

        public abstract String getText(RemoteR r);

        @Override
        public Font getToolTipFont(final Object element) {
            if (element instanceof RemoteR) {
                return JFaceResources.getTextFont();
            }
            return null;
        }

        @Override
        public String getToolTipText(final Object element) {
            if (element instanceof RemoteR) {
                return ((RemoteR) element).createSummary();
            }
            return null;
        }

    }

    private Combo fHostAddressControl;

    private TreeViewer fRServerViewer;

    private List<RemoteR> fRServerList;

    private final boolean fFilterOnlyRunning;

    private String fUsername;

    private final List<String> fHistoryAddress = new ArrayList<String>(DialogUtil.HISTORY_MAX + 10);
    private final List<String> fAdditionalAddress = new ArrayList<String>(8);
    private final Map<String, SpecialAddress> fSpecialAddress = new HashMap<String, SpecialAddress>(8);
    private String fInitialAddress;

    public RRemoteConsoleSelectionDialog(final Shell parentShell, final boolean onlyRunning) {
        super(parentShell);
        setTitle(RConsoleMessages.RRemoteConsoleSelectionDialog_title);
        setMessage(RConsoleMessages.RRemoteConsoleSelectionDialog_message);

        setStatusLineAboveButtons(true);
        setDialogBoundsSettings(getDialogSettings(), Dialog.DIALOG_PERSISTSIZE);

        fUsername = System.getProperty("user.name"); //$NON-NLS-1$
        fFilterOnlyRunning = onlyRunning;
    }

    public void setUser(final String username) {
        if (username != null && username.length() > 0) {
            fUsername = username;
        }
    }

    public void setInitialAddress(final String address) {
        fAdditionalAddress.remove(address);
        fAdditionalAddress.add(0, address);
        fInitialAddress = address;
    }

    public void addAdditionalAddress(final String label, final SpecialAddress factory) {
        fAdditionalAddress.add(label);
        if (factory != null) {
            fSpecialAddress.put(label, factory);
        }
    }

    public void clearAdditionaAddress(final boolean specialOnly) {
        if (specialOnly) {
            for (final Entry<String, ?> entry : fSpecialAddress.entrySet()) {
                fAdditionalAddress.remove(entry.getKey());
            }
        } else {
            fAdditionalAddress.clear();
        }
        fSpecialAddress.clear();
    }

    protected IDialogSettings getDialogSettings() {
        return DialogUtil.getDialogSettings(RConsoleUIPlugin.getDefault(), SETTINGS_DIALOG_ID);
    }

    @Override
    protected Control createContents(final Composite parent) {
        //      PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, "de.walware.statet.r.ui.remote_engine_selection_dialog"); //$NON-NLS-1$

        return super.createContents(parent);
    }

    @Override
    protected Control createDialogArea(final Composite parent) {
        // page group
        final Composite area = (Composite) super.createDialogArea(parent);

        createMessageArea(area);
        final IDialogSettings dialogSettings = getDialogSettings();

        {
            final Composite composite = new Composite(area, SWT.NONE);
            composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            composite.setLayout(LayoutUtil.applyCompositeDefaults(new GridLayout(), 3));

            final Label label = new Label(composite, SWT.NONE);
            label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            label.setText(RConsoleMessages.RRemoteConsoleSelectionDialog_Hostname_label);

            fHostAddressControl = new Combo(composite, SWT.DROP_DOWN);
            final GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = LayoutUtil.hintWidth(fHostAddressControl, 50);
            fHostAddressControl.setLayoutData(gd);
            final String[] history = dialogSettings.getArray(SETTINGS_HOST_HISTORY_KEY);
            if (history != null) {
                fHistoryAddress.addAll(Arrays.asList(history));
            }
            fHostAddressControl.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetDefaultSelected(final SelectionEvent e) {
                    update();
                }
            });

            final Button goButton = new Button(composite, SWT.PUSH);
            goButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            goButton.setText(RConsoleMessages.RRemoteConsoleSelectionDialog_Update_label);
            goButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    update();
                }
            });
        }

        {
            final TreeComposite composite = new TreeComposite(area, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION);
            final GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
            gd.heightHint = LayoutUtil.hintHeight(composite.tree, 10);
            composite.setLayoutData(gd);
            fRServerViewer = composite.viewer;
            composite.tree.setHeaderVisible(true);
            ColumnViewerToolTipSupport.enableFor(composite.viewer);

            {
                final TreeViewerColumn column = new TreeViewerColumn(fRServerViewer, SWT.NONE);
                column.getColumn().setText(RConsoleMessages.RRemoteConsoleSelectionDialog_Table_UserOrEngine_label);
                composite.layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
                column.setLabelProvider(new RemoteRLabelProvider() {
                    @Override
                    public void update(final ViewerCell cell) {
                        final Object element = cell.getElement();
                        String text = null;
                        Image image = null;
                        if (element instanceof String) {
                            text = (String) element;
                            image = SharedUIResources.getImages().get(SharedUIResources.OBJ_USER_IMAGE_ID);
                        } else if (element instanceof RemoteR) {
                            text = getText((RemoteR) element);
                        }
                        cell.setText(text);
                        cell.setImage(image);
                    }

                    @Override
                    public String getText(final RemoteR r) {
                        return r.info.getName();
                    }
                });
            }
            {
                final TreeViewerColumn column = new TreeViewerColumn(fRServerViewer, SWT.NONE);
                column.getColumn().setText(RConsoleMessages.RRemoteConsoleSelectionDialog_Table_Host_label);
                composite.layout.setColumnData(column.getColumn(), new ColumnWeightData(1));
                column.setLabelProvider(new RemoteRLabelProvider() {
                    @Override
                    public String getText(final RemoteR r) {
                        return r.hostName;
                    }
                });
            }

            fRServerViewer.setContentProvider(new RemoteRContentProvider());

            fRServerViewer.getTree().addSelectionListener(new SelectionListener() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    updateState();
                }

                @Override
                public void widgetDefaultSelected(final SelectionEvent e) {
                    updateState();
                    if (getOkButton().isEnabled()) {
                        buttonPressed(IDialogConstants.OK_ID);
                    }
                }
            });
        }

        Dialog.applyDialogFont(area);

        updateInput();
        if (fRServerList != null) {
            updateStatus(new Status(IStatus.OK, RConsoleUIPlugin.PLUGIN_ID,
                    RConsoleMessages.RRemoteConsoleSelectionDialog_info_ListRestored_message));
        }
        return area;
    }

    private void update() {
        final String input = fHostAddressControl.getText();
        fRServerList = null;
        final AtomicReference<IStatus> status = new AtomicReference<IStatus>();
        if (input != null && input.length() > 0) {
            try {
                new ProgressMonitorDialog(getShell()).run(true, true, new IRunnableWithProgress() {
                    @Override
                    public void run(final IProgressMonitor monitor) throws InvocationTargetException {
                        status.set(updateRServerList(input, monitor));
                    }
                });
            } catch (final InvocationTargetException e) {
                // not used
            } catch (final InterruptedException e) {
                fRServerViewer = null;
                status.compareAndSet(null, Status.CANCEL_STATUS);
            }
        }
        if (status.get() != null) {
            updateStatus(status.get());
        }
        getOkButton().setEnabled(false);
        if (fRServerList != null && fRServerList.size() > 0) {
            if (!fSpecialAddress.containsKey(input)) {
                fHistoryAddress.remove(input);
                fHistoryAddress.add(0, input);
            }
            if (fFilterOnlyRunning) {
                for (final Iterator<RemoteR> iter = fRServerList.iterator(); iter.hasNext();) {
                    switch (iter.next().info.getState()) {
                    case Server.S_NOT_STARTED:
                    case Server.S_STOPPED:
                        iter.remove();
                    }
                }
            }
            updateInput();
            return;
        } else {
            fRServerViewer.setInput(null);
        }
    }

    private void updateInput() {
        final String selectedAddress = fHostAddressControl.getText();
        final List<String> list = new ArrayList<String>(fHistoryAddress.size() + fAdditionalAddress.size());
        list.addAll(fHistoryAddress);
        for (final String address : fAdditionalAddress) {
            if (!list.contains(address)) {
                list.add(address);
            }
        }
        fHostAddressControl.setItems(list.toArray(new String[list.size()]));

        fRServerViewer.setInput(fRServerList);

        if (fUsername != null && fUsername.length() > 0) {
            Display.getCurrent().asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (fInitialAddress != null) {
                        fHostAddressControl.setText(fInitialAddress);
                        fInitialAddress = null;
                    } else if (selectedAddress != null && selectedAddress.length() > 0) {
                        fHostAddressControl.setText(selectedAddress);
                    } else if (fHostAddressControl.getItemCount() > 0) {
                        fHostAddressControl.select(0);
                    }
                    fRServerViewer.expandToLevel(fUsername.toLowerCase(), 1);
                    updateState();
                }
            });
        }
    }

    private void updateState() {
        final IStructuredSelection selection = (IStructuredSelection) fRServerViewer.getSelection();
        getOkButton().setEnabled(selection.getFirstElement() instanceof RemoteR);
    }

    @Override
    protected void computeResult() {
        final IStructuredSelection selection = (IStructuredSelection) fRServerViewer.getSelection();
        final Object element = selection.getFirstElement();
        if (element instanceof RemoteR) {
            setSelectionResult(new Object[] { ((RemoteR) element).address });
        }
    }

    @Override
    public boolean close() {
        final IDialogSettings dialogSettings = getDialogSettings();
        dialogSettings.put(SETTINGS_HOST_HISTORY_KEY, fHistoryAddress.toArray(new String[fHistoryAddress.size()]));

        return super.close();
    }

    private IStatus updateRServerList(final String combined, final IProgressMonitor monitor) {
        final List<RemoteR> infos = new ArrayList<RemoteR>();

        final String[] addresses = ADDRESS_MULTI_PATTERN.split(combined, -1);
        if (addresses.length == 0) {
            return null;
        }
        final SubMonitor progress = SubMonitor.convert(monitor,
                RConsoleMessages.RRemoteConsoleSelectionDialog_task_Gathering_message, addresses.length * 2 + 2);

        String failedHosts = null;
        final List<IStatus> failedStatus = new ArrayList<IStatus>();
        progress.worked(1);

        // Collect R engines for each address
        for (int i = 0; i < addresses.length; i++) {
            progress.setWorkRemaining((addresses.length - i) * 2 + 1);

            String address = addresses[i];
            final SpecialAddress special = fSpecialAddress.get(address);
            if (special == null) {
                if (address.startsWith("rmi:")) { //$NON-NLS-1$
                    address = address.substring(4);
                }
                if (address.startsWith("//")) { //$NON-NLS-1$
                    address = address.substring(2);
                }
            }
            if (address.isEmpty()) {
                return null;
            }
            if (monitor.isCanceled()) {
                return Status.CANCEL_STATUS;
            }
            IStatus status;
            if (special == null) {
                final Matcher matcher = ADDRESS_WITH_PORT_PATTERN.matcher(address);
                if (matcher.matches()) {
                    status = collectServerInfos(matcher.group(1), Integer.parseInt(matcher.group(2)), null, infos,
                            progress);
                } else {
                    status = collectServerInfos(address, Registry.REGISTRY_PORT, null, infos, progress);
                }
            } else {
                address = special.fPublicHost;
                status = collectServerInfos(null, 0, special, infos, progress);
            }
            switch (status.getSeverity()) {
            case IStatus.CANCEL:
                return status;
            case IStatus.ERROR:
                StatusManager.getManager().handle(status, StatusManager.LOG);
                return status;
            case IStatus.WARNING:
                failedStatus.add(status);
                failedHosts = (failedHosts == null) ? address : (failedHosts + ", " + address); //$NON-NLS-1$
                continue;
            default:
                continue;
            }
        }

        if (!failedStatus.isEmpty()) {
            StatusManager.getManager()
                    .handle(new MultiStatus(RConsoleUIPlugin.PLUGIN_ID, 0,
                            failedStatus.toArray(new IStatus[failedStatus.size()]),
                            "Info about connection failures when browsing R engines:", null), //$NON-NLS-1$
                            StatusManager.LOG);
        }
        if (!infos.isEmpty() || failedStatus.isEmpty()) {
            fRServerList = infos;
        }

        if (failedHosts != null) {
            return new Status(IStatus.WARNING, RConsoleUIPlugin.PLUGIN_ID,
                    RConsoleMessages.RRemoteConsoleSelectionDialog_error_ConnectionFailed_message + failedHosts);
        }
        return Status.OK_STATUS;
    }

    private static IStatus collectServerInfos(String address, int port, final SpecialAddress special,
            final List<RemoteR> infos, final SubMonitor progress) {
        try {
            if (special != null) {
                address = special.fPublicHost;
                port = special.fPort;
            }
            progress.subTask(
                    NLS.bind(RConsoleMessages.RRemoteConsoleSelectionDialog_task_Resolving_message, address));
            final InetAddress inetAddress = InetAddress.getByName(address);
            final String hostname = inetAddress.getHostName();
            final String hostip = inetAddress.getHostAddress();
            progress.worked(1);
            if (progress.isCanceled()) {
                return Status.CANCEL_STATUS;
            }

            progress.subTask(
                    NLS.bind(RConsoleMessages.RRemoteConsoleSelectionDialog_task_Connecting_message, hostname));
            final Registry registry;
            if (special != null) {
                final RMIClientSocketFactory socketFactory = special.getSocketFactory(progress.newChild(5));
                RjsComConfig.setRMIClientSocketFactory(socketFactory);
                registry = LocateRegistry.getRegistry(special.fPrivateHost, port, socketFactory);
            } else {
                RjsComConfig.setRMIClientSocketFactory(null);
                registry = LocateRegistry.getRegistry(address, port);
            }
            final String rmiBase = (port == Registry.REGISTRY_PORT) ? "//" + address + '/' : //$NON-NLS-1$
                    "//" + address + ':' + port + '/'; //$NON-NLS-1$
            final String[] names = registry.list();
            for (final String name : names) {
                try {
                    final Remote remote = registry.lookup(name);
                    if (remote instanceof Server) {
                        final Server server = (Server) remote;
                        final ServerInfo info = server.getInfo();
                        final String rmiAddress = rmiBase + name;
                        final RemoteR r = new RemoteR(hostname, hostip, rmiAddress, info);
                        infos.add(r);
                    }
                } catch (final Exception e) {
                }
            }
            return Status.OK_STATUS;
        } catch (final RemoteException e) {
            return new Status(IStatus.WARNING, RConsoleUIPlugin.PLUGIN_ID, address);
        } catch (final UnknownHostException e) {
            return new Status(IStatus.ERROR, RConsoleUIPlugin.PLUGIN_ID,
                    "Unknown host: " + e.getLocalizedMessage()); //$NON-NLS-1$
        } catch (final CoreException e) {
            return e.getStatus();
        } finally {
            RjsComConfig.clearRMIClientSocketFactory();
        }
    }

}