com.google.appengine.eclipse.core.deploy.ui.DeployProjectDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.google.appengine.eclipse.core.deploy.ui.DeployProjectDialog.java

Source

/*******************************************************************************
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.google.appengine.eclipse.core.deploy.ui;

import com.google.appengine.eclipse.core.AppEngineCorePlugin;
import com.google.appengine.eclipse.core.AppEngineCorePluginLog;
import com.google.appengine.eclipse.core.nature.GaeNature;
import com.google.appengine.eclipse.core.properties.GaeProjectProperties;
import com.google.appengine.eclipse.core.properties.ui.GaeProjectPropertyPage;
import com.google.appengine.eclipse.core.resources.GaeImages;
import com.google.appengine.eclipse.core.resources.GaeProject;
import com.google.gdt.eclipse.core.StatusUtilities;
import com.google.gdt.eclipse.core.browser.BrowserUtilities;
import com.google.gdt.eclipse.core.deploy.DeploymentSet;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.window.Window;
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.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.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.osgi.service.prefs.BackingStoreException;

import java.util.ArrayList;
import java.util.List;

/**
 * Gets the parameters for deploying an App Engine project to Google.
 */
public class DeployProjectDialog extends TitleAreaDialog {

    private class DeployTree {

        // backends will hopefully not have names with a pipe
        private static final String SETTINGS_DELIMITER = "|";

        private TreeItem backendsItem; // may be null
        private TreeItem frontendsItem;
        private Tree tree;

        public DeployTree(Composite parent, Object layoutData) {
            tree = new Tree(parent, SWT.MULTI | SWT.BORDER | SWT.CHECK);
            tree.setLayoutData(layoutData);

            tree.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    Object o = e.item.getData();
                    if (o != null && o instanceof Runnable) {
                        ((Runnable) o).run();
                    }

                    // Don't call fieldChanged() in the tree listener because
                    // fieldChanged()
                    // will rebuild the tree, which is for project changes. Instead just
                    // call updateStatus directly.
                    updateStatus(validate());
                }
            });
        }

        public DeploymentSet getDeploymentSet() {
            boolean deployFrontend = (frontendsItem != null && frontendsItem.getChecked());

            List<String> backendNames = new ArrayList<String>();
            if (backendsItem != null) {
                for (TreeItem i : backendsItem.getItems()) {
                    if (i.getChecked()) {
                        backendNames.add(i.getText());
                    }
                }
            }

            return new DeploymentSet(deployFrontend, backendNames);
        }

        public String getSettings() {

            if (!allTreeItemsChecked()) {

                // pipe-delimited settings
                StringBuilder sb = new StringBuilder();
                for (TreeItem n : backendsItem.getItems()) {
                    if (n.getChecked()) {
                        String backendName = n.getText();
                        sb.append(backendName);
                        sb.append(SETTINGS_DELIMITER);
                    }
                }

                /*
                 * Append whether the frontend item is checked to the end, benefiting
                 * from the leftover delimiter from the above for loop. Note that there
                 * must be backends, and hence a leftover delimiter, to even get here,
                 * because if there were only a frontend, that is checked by default and
                 * the user can't uncheck it, and allTreeItemsChecked() will return
                 * true.
                 */
                sb.append(frontendsItem.getChecked() + "");

                return sb.toString();
            } else {

                /*
                 * Don't save the default settings (ie, all checked). This handles the
                 * case where a project originally has no backends, and then backends
                 * are added later. If we saved "no backends checked", then the newly
                 * added backends will not be checked when these settings are loaded
                 * (ie, overriding defaults)
                 */
                return "";
            }
        }

        public void setTreeContents(IProject newProject) {
            tree.removeAll();
            frontendsItem = null;
            backendsItem = null;

            if (newProject != null && GaeNature.isGaeProject(newProject)) {

                GaeProject gaeProject = GaeProject.create(newProject);

                // Add "Frontend" TreeItem
                tree.setEnabled(true);
                frontendsItem = new TreeItem(tree, SWT.NONE);
                frontendsItem.setText("Frontend");

                List<String> backendNames = gaeProject.getBackendNames();
                if (backendNames.size() > 0) {

                    // Add "Backends" TreeItem
                    backendsItem = new TreeItem(tree, SWT.NONE);
                    backendsItem.setText("Backends");
                    // when the parent TreeItem is checked or unchecked, check or uncheck
                    // all its children.
                    backendsItem.setData(new Runnable() {
                        public void run() {
                            boolean checked = backendsItem.getChecked();
                            backendsItem.setGrayed(false);
                            for (TreeItem i : backendsItem.getItems()) {
                                i.setChecked(checked);
                            }
                        }
                    });

                    /*
                     * TreeItem doesn't support SWT.Selection listeners, so give it a
                     * runnable and call this runnable in the tree's selection event. See
                     * DeployTree constructor. When a child TreeItem of backends is checked or
                     * unchecked, determine if the parent TreeItem should be checked (ie,
                     * checked if all children are checked, unchecked if all children are
                     * unchecked, and grayed if some are checked).
                     */
                    Runnable backendItemRunnable = new Runnable() {
                        public void run() {
                            setCheckedStateFromChildren(backendsItem);
                        }
                    };

                    // Add all backend TreeItems
                    for (String s : backendNames) {
                        TreeItem backendItem = new TreeItem(backendsItem, SWT.NONE);
                        backendItem.setText(s);
                        backendItem.setData(backendItemRunnable);
                    }

                    backendsItem.setExpanded(true);

                    String settings = GaeProjectProperties.getGaeDeployDialogSettings(newProject);
                    loadCheckedStates(settings);

                } else {
                    // if there is a project and it's a gae project, but there are no
                    // backends, then add only the frontend tree item and disable the tree
                    frontendsItem.setChecked(true);
                    tree.setEnabled(false);
                }

            } else {
                // if there is no project or it isn't a gae project, then the tree
                // is cleared and disabled.
                tree.setEnabled(false);
            }
        }

        public IStatus validate() {

            if (countAllCheckedTreeItems() == 0) {
                return StatusUtilities.newErrorStatus("Select at least the frontend or one backend to deploy.",
                        AppEngineCorePlugin.PLUGIN_ID);
            } else {
                return OK_STATUS;
            }
        }

        private boolean allTreeItemsChecked() {
            // tree is max 2 levels deep
            for (TreeItem n : tree.getItems()) {
                if (!n.getChecked()) {
                    return false;
                }
                for (TreeItem m : tree.getItems()) {
                    if (!m.getChecked()) {
                        return false;
                    }
                }
            }
            return true;
        }

        private int countAllCheckedTreeItems() {
            int totalCheckedItems = 0;
            for (int i = 0; i < tree.getItemCount(); i++) {
                TreeItem item = tree.getItem(i);
                if (item.getChecked()) {
                    totalCheckedItems++;
                }
                // countCheckedTreeItems doesn't count child nodes recursively, but the 
                // tree is only two levels deep, so this is ok.
                totalCheckedItems += countCheckedChildTreeItems(item);
            }
            return totalCheckedItems;
        }

        /**
         * Counts the number of checked TreeItems under this TreeItem. This does not
         * count child nodes.
         */
        private int countCheckedChildTreeItems(TreeItem item) {
            int checkedItemsCount = 0;
            for (int i = 0; i < item.getItemCount(); i++) {
                TreeItem t = item.getItem(i);
                if (t.getChecked()) {
                    checkedItemsCount++;
                }
            }
            return checkedItemsCount;
        }

        private void loadCheckedStates(String settings) {

            if (settings.length() > 0) {

                String[] strings = settings.split("[" + SETTINGS_DELIMITER + "]");

                for (int i = 0; i < strings.length - 1; i++) {
                    for (TreeItem n : backendsItem.getItems()) {
                        if (n.getText().equals(strings[i])) {
                            n.setChecked(true);
                        }
                    }
                }

                setCheckedStateFromChildren(backendsItem);

                boolean frontendsItemChecked = "true".equals(strings[strings.length - 1]);
                frontendsItem.setChecked(frontendsItemChecked);
            } else {
                // tree is only ever 2 levels deep
                for (TreeItem n : tree.getItems()) {
                    n.setChecked(true);
                    for (TreeItem m : n.getItems()) {
                        m.setChecked(true);
                    }
                }
            }
        }

        private void setCheckedStateFromChildren(TreeItem n) {
            int checkedItemsCount = countCheckedChildTreeItems(n);
            if (checkedItemsCount == 0) {
                n.setChecked(false);
            } else if (checkedItemsCount == n.getItemCount()) {
                n.setChecked(true);
                n.setGrayed(false);
            } else {
                n.setChecked(true);
                n.setGrayed(true);
            }
        }
    }

    private class FieldListener implements ModifyListener {
        public void modifyText(ModifyEvent e) {
            fieldChanged();
        }
    }

    private static final Status OK_STATUS = new Status(IStatus.OK, AppEngineCorePlugin.PLUGIN_ID, "");

    public static IStatus getMostImportantStatus(IStatus[] status) {
        IStatus max = null;
        for (int i = 0; i < status.length; i++) {
            IStatus curr = status[i];
            if (curr.matches(IStatus.ERROR)) {
                return curr;
            }
            if (max == null || curr.getSeverity() > max.getSeverity() || max.getMessage().length() == 0) {
                max = curr;
            }
        }
        return max;
    }

    private static int convertSeverity(IStatus status) {
        switch (status.getSeverity()) {
        case IStatus.ERROR:
            return IMessageProvider.ERROR;
        case IStatus.WARNING:
            return IMessageProvider.WARNING;
        case IStatus.INFO:
            return IMessageProvider.INFORMATION;
        default:
            return IMessageProvider.NONE;
        }
    }

    private String appId;

    private String appVersion;

    private Button chooseProjectButton;

    private Button deployButton;

    private DeployTree deployTree;

    private DeploymentSet deploymentSet;

    private final FieldListener listener = new FieldListener();

    private Link moreInfoLink;

    // TODO: this really should be a GaeProject instance
    private IProject project;

    private Link projectPropertiesLink;

    private Text projectText;

    public DeployProjectDialog(IProject project, Shell parentShell) {
        super(parentShell);
        this.project = project;
    }

    public DeploymentSet getDeploymentSet() {
        return deploymentSet;
    }

    public IProject getProject() {
        return project;
    }

    @Override
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        newShell.setText("Deploy Project to Google App Engine");
        setHelpAvailable(false);
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        super.createButtonsForButtonBar(parent);
        deployButton = getButton(IDialogConstants.OK_ID);

        // Re-label the OK button and set it as default
        deployButton.setText("Deploy");
        getShell().setDefaultButton(deployButton);
    }

    @Override
    protected Control createContents(Composite parent) {
        Control contents = super.createContents(parent);

        setTitle("Deploy");
        setTitleImage(AppEngineCorePlugin.getDefault().getImage(GaeImages.APP_ENGINE_DEPLOY_LARGE));

        initializeControls();
        addEventHandlers();
        fieldChanged();

        return contents;
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        parent = (Composite) super.createDialogArea(parent);

        Composite container = new Composite(parent, SWT.NONE);
        GridData containerGridData = new GridData(GridData.FILL_HORIZONTAL);
        container.setLayoutData(containerGridData);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 3;
        gridLayout.marginHeight = 8;
        gridLayout.marginWidth = 8;
        gridLayout.makeColumnsEqualWidth = false;
        container.setLayout(gridLayout);

        // Project field
        Label projectLabel = new Label(container, SWT.NONE);
        projectLabel.setText("Project:");
        projectText = new Text(container, SWT.BORDER);
        projectText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        chooseProjectButton = new Button(container, SWT.NONE);
        chooseProjectButton.setText("Browse...");

        // spacer
        new Label(container, SWT.NONE)
                .setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, true, true, 3, 1));

        // Frontends / backends deploy tree
        Label treeLabel = new Label(container, SWT.NONE);
        treeLabel.setText("Select the frontend and backends to deploy:");
        treeLabel.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER, true, true, 2, 1));

        moreInfoLink = new Link(container, SWT.NONE);
        moreInfoLink.setText("<a href=\"#\">Learn more</a>");
        moreInfoLink.setLayoutData(new GridData(GridData.END, GridData.CENTER, false, false, 1, 1));

        GridData treeGridData = new GridData(GridData.FILL_BOTH);
        treeGridData.horizontalSpan = 3;
        treeGridData.minimumHeight = 150;
        treeGridData.heightHint = 150;
        deployTree = new DeployTree(container, treeGridData);

        projectPropertiesLink = new Link(container, SWT.NONE);
        GridData createAppIdLinkGridData = new GridData(SWT.LEAD, SWT.TOP, true, false, 3, 1);
        createAppIdLinkGridData.verticalIndent = 15;
        projectPropertiesLink.setLayoutData(createAppIdLinkGridData);
        projectPropertiesLink.setText("<a href=\"#\">App Engine project settings...</a>");

        return container;
    }

    @Override
    protected void okPressed() {

        this.deploymentSet = deployTree.getDeploymentSet();

        String settings = deployTree.getSettings();

        try {
            GaeProjectProperties.setGaeDeployDialogSettings(project, settings);
        } catch (BackingStoreException e) {
            AppEngineCorePluginLog.logError(e, "Could not save deploy dialog settings.");
        }

        // do this last so that the widgets aren't disposed so that info
        // can be still taken from them in deployTree.getSettings()
        super.okPressed();
    }

    private void addEventHandlers() {
        projectText.addModifyListener(listener);
        chooseProjectButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IJavaProject selectedProject = chooseProject();
                if (selectedProject != null) {
                    projectText.setText(selectedProject.getElementName());
                }
            }
        });

        projectPropertiesLink.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                assert (project != null);
                PreferenceDialog page = PreferencesUtil.createPropertyDialogOn(DeployProjectDialog.this.getShell(),
                        project, GaeProjectPropertyPage.ID, new String[] { GaeProjectPropertyPage.ID }, null);
                if (Window.OK == page.open()) {
                    // Invalidate cached app id and version
                    GaeProject gaeProject = GaeProject.create(project);
                    assert (gaeProject != null);

                    appId = gaeProject.getAppId();
                    appVersion = gaeProject.getAppVersion();

                    // Refresh to pick up settings changes
                    fieldChanged();
                }
            }
        });

        moreInfoLink.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                BrowserUtilities
                        .launchBrowserAndHandleExceptions("http://code.google.com/appengine/docs/java/backends");
            }
        });
    }

    private IJavaProject chooseProject() {
        IJavaProject[] javaProjects;
        try {
            javaProjects = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()).getJavaProjects();
        } catch (JavaModelException e) {
            AppEngineCorePluginLog.logError(e);
            javaProjects = new IJavaProject[0];
        }

        // Filter the list to only show App Engine projects
        List<IJavaProject> gaeProjects = new ArrayList<IJavaProject>();
        for (IJavaProject javaProject : javaProjects) {
            if (GaeNature.isGaeProject(javaProject.getProject())) {
                gaeProjects.add(javaProject);
            }
        }

        ILabelProvider labelProvider = new JavaElementLabelProvider(JavaElementLabelProvider.SHOW_DEFAULT);
        ElementListSelectionDialog dialog = new ElementListSelectionDialog(getShell(), labelProvider);
        dialog.setTitle("Project Selection");
        dialog.setMessage("Choose a project to deploy");
        dialog.setElements(gaeProjects.toArray(new IJavaProject[0]));
        dialog.setInitialSelections(new Object[] { JavaCore.create(project) });

        dialog.setHelpAvailable(false);
        if (dialog.open() == Window.OK) {
            return (IJavaProject) dialog.getFirstResult();
        }
        return null;
    }

    private void fieldChanged() {
        validateFields();
        updateControls();
    }

    private void initializeControls() {
        // Set the project field if we have one set
        if (project != null) {
            projectText.setText(project.getName());

            // Initialize the app ID and version
            if (GaeNature.isGaeProject(project)) {
                GaeProject gaeProject = GaeProject.create(project);
                assert (gaeProject != null);

                appId = gaeProject.getAppId();
                appVersion = gaeProject.getAppVersion();
            }
        }
    }

    private void updateControls() {
        projectPropertiesLink.setEnabled(project != null);
        deployTree.setTreeContents(project);
    }

    private void updateStatus(IStatus status) {
        boolean allFieldsValid = false;

        if (status.getSeverity() == IStatus.OK && status.getMessage().length() == 0) {
            String msg = "Ready to deploy application '" + appId + "', version " + appVersion;

            status = new Status(IStatus.INFO, AppEngineCorePlugin.PLUGIN_ID, msg);
            allFieldsValid = true;
        }

        if (status.getSeverity() == IStatus.INFO || status.getSeverity() == IStatus.WARNING) {
            allFieldsValid = true;
        }

        this.setMessage(status.getMessage(), convertSeverity(status));
        deployButton.setEnabled(allFieldsValid);
    }

    private void updateStatus(IStatus[] status) {
        updateStatus(getMostImportantStatus(status));
    }

    private void validateFields() {
        IStatus projectStatus = validateProject();
        // don't need to call validateDeployTree() here, see addEventHandlers()
        updateStatus(new IStatus[] { projectStatus });
    }

    private IStatus validateProject() {
        IProject originalProject = project;
        project = null;

        String projectName = projectText.getText().trim();
        if (projectName.length() == 0) {
            return StatusUtilities.newOkStatus("Enter the project name", AppEngineCorePlugin.PLUGIN_ID);
        }

        IProject enteredProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
        if (!enteredProject.exists()) {
            return StatusUtilities.newErrorStatus("Project does not exist", AppEngineCorePlugin.PLUGIN_ID);
        }

        if (!enteredProject.isOpen()) {
            return StatusUtilities.newErrorStatus("Project is not open", AppEngineCorePlugin.PLUGIN_ID);
        }

        if (!GaeNature.isGaeProject(enteredProject)) {
            return StatusUtilities.newErrorStatus((projectName + " is not an App Engine project"),
                    AppEngineCorePlugin.PLUGIN_ID);
        }

        // Update the project now that we know we have an App Engine nature
        project = enteredProject;

        // If we've changed projects, update the cached app ID and version
        if (!enteredProject.equals(originalProject)) {
            GaeProject gaeProject = GaeProject.create(enteredProject);
            appId = gaeProject.getAppId();
            appVersion = gaeProject.getAppVersion();
        }

        // Validate that the project has an app ID and version
        if (appId == null || appId.length() == 0) {
            return StatusUtilities.newErrorStatus((projectName
                    + " does not have an application ID.\nClick the project settings link below to set it."),
                    AppEngineCorePlugin.PLUGIN_ID);
        }
        if (appVersion == null || appVersion.length() == 0) {
            return StatusUtilities.newErrorStatus(
                    (projectName + " does not have a version.\nClick the project settings link below to set it."),
                    AppEngineCorePlugin.PLUGIN_ID);
        }

        return OK_STATUS;
    }
}