com.amazonaws.eclipse.lambda.project.wizard.page.NewLambdaJavaFunctionProjectWizardPageOne.java Source code

Java tutorial

Introduction

Here is the source code for com.amazonaws.eclipse.lambda.project.wizard.page.NewLambdaJavaFunctionProjectWizardPageOne.java

Source

/*
 * Copyright 2010-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.amazonaws.eclipse.lambda.project.wizard.page;

import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newCheckbox;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newCombo;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newControlDecoration;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newFillingLabel;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newGroup;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLink;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText;
import static com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel.P_CUSTOM_HANDLER_INPUT_TYPE;
import static com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel.P_HANDLER_CLASS_NAME;
import static com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel.P_HANDLER_OUTPUT_TYPE;
import static com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel.P_HANDLER_PACKAGE_NAME;
import static com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel.P_SHOW_README_FILE;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Iterator;

import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer;
import org.eclipse.jdt.internal.ui.text.SimpleJavaSourceViewerConfiguration;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.JavaTextTools;
import org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import com.amazonaws.eclipse.databinding.BooleanValidator;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.databinding.NotEmptyValidator;
import com.amazonaws.eclipse.lambda.LambdaPlugin;
import com.amazonaws.eclipse.lambda.UrlConstants;
import com.amazonaws.eclipse.lambda.project.classpath.LambdaRuntimeClasspathContainer;
import com.amazonaws.eclipse.lambda.project.classpath.runtimelibrary.LambdaRuntimeLibraryManager;
import com.amazonaws.eclipse.lambda.project.template.CodeTemplateManager;
import com.amazonaws.eclipse.lambda.project.wizard.model.NewLambdaJavaFunctionProjectWizardDataModel;
import com.amazonaws.eclipse.lambda.project.wizard.model.PredefinedHandlerInputType;
import com.amazonaws.eclipse.lambda.project.wizard.page.validator.ValidPackageNameValidator;
import com.amazonaws.eclipse.sdk.ui.JavaSdkManager;
import com.amazonaws.eclipse.sdk.ui.classpath.AwsClasspathContainer;

import freemarker.template.Template;

@SuppressWarnings("restriction")
public class NewLambdaJavaFunctionProjectWizardPageOne extends NewJavaProjectWizardPageOne {

    private final NewLambdaJavaFunctionProjectWizardDataModel dataModel;
    private final DataBindingContext bindingContext;
    private final AggregateValidationStatus aggregateValidationStatus;

    private boolean isProjectNameValid;

    /* Function handler section */
    private Text handlerPackageText;
    private ControlDecoration handlerPackageTextDecoration;
    private ISWTObservableValue handlerPackageTextObservable;

    private Text handlerClassText;
    private ControlDecoration handlerClassTextDecoration;
    private ISWTObservableValue handlerClassTextObservable;

    private Combo predefinedHandlerInputCombo;

    private Text customHandlerInputTypeText;
    private ControlDecoration customHandlerInputTypeTextDecoration;
    private ISWTObservableValue customHandlerInputTypeTextObservable;
    private IObservableValue enableCustomHandlerInputTypeValidation = new WritableValue();

    private IObservableValue enableSdkInstalledValidation = new WritableValue();
    private IObservableValue sdkInstalledObservable = new WritableValue();

    private Text handlerOutputTypeText;
    private ControlDecoration handlerOutputTypeTextDecoration;
    private ISWTObservableValue handlerOutputTypeTextObservable;

    private JavaSourceViewer sourcePreview;
    private Document sourcePreviewDocument;
    private final Template sourceTemplate;

    private static String CUSTOM_INPUT_TYPE_COMBO_TEXT = "Custom";
    private static Object CUSTOM_INPUT_TYPE_COMBO_DATA = new Object();

    /* Check box to opt-out showing README.html */
    private Button showReadmeFileCheckbox;
    private ISWTObservableValue showReadmeFileCheckboxObservable;

    public NewLambdaJavaFunctionProjectWizardPageOne(NewLambdaJavaFunctionProjectWizardDataModel dataModel) {
        setTitle("Create a new AWS Lambda Java project");
        setDescription("Create a new AWS Lambda Java project in the workspace");

        this.dataModel = dataModel;
        this.bindingContext = new DataBindingContext();
        this.aggregateValidationStatus = new AggregateValidationStatus(bindingContext,
                AggregateValidationStatus.MAX_SEVERITY);

        this.sourceTemplate = CodeTemplateManager.getInstance().getHandlerClassTemplate();
    }

    @Override
    public void createControl(final Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(new GridLayout(1, false));

        // Reuse the project name control of the system Java project wizard
        Control nameControl = createNameControl(composite);
        nameControl.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        createFunctionHandlerSection(composite);
        linkPreviewWithHandlerConfigInput();

        createShowReadmeFileCheckBox(composite);

        bindControls();
        initializeValidators();
        initializeDefaults();

        setControl(composite);
    }

    private void createFunctionHandlerSection(Composite composite) {
        Group group = newGroup(composite, "Lambda Function Handler");
        GridLayout groupLayout = new GridLayout(1, true);
        groupLayout.marginWidth = 15;
        group.setLayout(groupLayout);

        String description = "Each Lambda function must specify a handler class "
                + "which the service will use as the entry point to begin execution.";
        setItalicFont(newLink(group, UrlConstants.webLinkListener,
                description + " <a href=\"" + UrlConstants.LAMBDA_EXECUTION_ROLE_DOC_URL
                        + "\">Learn more</a> about Lambda Java function handler.",
                1));

        Composite inputComposite = new Composite(group, SWT.NONE);
        inputComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
        GridLayout inputCompLayout = new GridLayout(3, true);
        inputCompLayout.marginHeight = 10;
        inputCompLayout.marginWidth = 10;
        inputComposite.setLayout(inputCompLayout);

        newFillingLabel(inputComposite, "Package Name:");
        handlerPackageText = newText(inputComposite, "", 2);
        handlerPackageTextDecoration = newControlDecoration(handlerPackageText, "");

        newFillingLabel(inputComposite, "Class Name:");
        handlerClassText = newText(inputComposite, "", 2);
        handlerClassTextDecoration = newControlDecoration(handlerClassText, "");

        newFillingLabel(inputComposite, "Input Type:");
        predefinedHandlerInputCombo = createPredefinedHandlerInputTypeCombo(inputComposite, 1);
        customHandlerInputTypeText = newText(inputComposite, "", 1);
        customHandlerInputTypeTextDecoration = newControlDecoration(customHandlerInputTypeText, "");

        newFillingLabel(inputComposite, "Output Type:");
        handlerOutputTypeText = newText(inputComposite, "", 2);
        handlerOutputTypeTextDecoration = newControlDecoration(handlerOutputTypeText, "");

        Label separator = new Label(group, SWT.HORIZONTAL | SWT.SEPARATOR);
        separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        createHandlerSourcePreview(group);
    }

    private Combo createPredefinedHandlerInputTypeCombo(Composite composite, int colspan) {

        final Combo combo = newCombo(composite, 1);

        for (PredefinedHandlerInputType type : PredefinedHandlerInputType.values()) {
            combo.add(type.getDisplayName());
            combo.setData(type.getDisplayName(), type);
        }

        combo.add(CUSTOM_INPUT_TYPE_COMBO_TEXT);
        combo.setData(CUSTOM_INPUT_TYPE_COMBO_TEXT, CUSTOM_INPUT_TYPE_COMBO_DATA);

        combo.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                onPredefinedHandlerInputTypeComboSelectionChange();
            }
        });

        return combo;
    }

    private void onPredefinedHandlerInputTypeComboSelectionChange() {
        String selectedText = predefinedHandlerInputCombo.getText();
        Object selectedData = predefinedHandlerInputCombo.getData(selectedText);

        if (selectedData == CUSTOM_INPUT_TYPE_COMBO_DATA) {
            customHandlerInputTypeText.setEnabled(true);
            enableCustomHandlerInputTypeValidation.setValue(true);
            dataModel.setPredefinedHandlerInputType(null);

            enableSdkInstalledValidation.setValue(false);

        } else if (selectedData instanceof PredefinedHandlerInputType) {
            customHandlerInputTypeText.setEnabled(false);
            enableCustomHandlerInputTypeValidation.setValue(false);

            PredefinedHandlerInputType type = (PredefinedHandlerInputType) selectedData;
            dataModel.setPredefinedHandlerInputType(type);

            enableSdkInstalledValidation.setValue(type.requireSdkDependency());

        } else {
            LambdaPlugin.getDefault().warn("Unknown combo selection " + selectedText, null);
        }

    }

    private void createHandlerSourcePreview(Composite composite) {

        newFillingLabel(composite, "Preview:", 1);

        IPreferenceStore javaPluginPrefStore = JavaPlugin.getDefault().getCombinedPreferenceStore();

        sourcePreview = new JavaSourceViewer(composite, null, null, false, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER,
                javaPluginPrefStore);
        sourcePreview.setEditable(false);
        sourcePreview.getTextWidget().setFont(JFaceResources.getFont(PreferenceConstants.EDITOR_TEXT_FONT));

        // TODO: investigate why Java source coloring is not working
        // org.eclipse.jdt.internal.ui.preferences.formatter.JavaPreview
        JavaTextTools tools = JavaPlugin.getDefault().getJavaTextTools();
        SimpleJavaSourceViewerConfiguration sourceConfig = new SimpleJavaSourceViewerConfiguration(
                tools.getColorManager(), javaPluginPrefStore, null, IJavaPartitions.JAVA_PARTITIONING, true);
        sourcePreview.configure(sourceConfig);

        GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
        gridData.horizontalSpan = 1;
        gridData.heightHint = 50;
        sourcePreview.getTextWidget().setLayoutData(gridData);

        sourcePreviewDocument = new Document("");
        sourcePreview.setDocument(sourcePreviewDocument);
    }

    private void linkPreviewWithHandlerConfigInput() {

        this.dataModel.addPropertyChangeListener(new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent event) {

                NewLambdaJavaFunctionProjectWizardPageOne thisPage = NewLambdaJavaFunctionProjectWizardPageOne.this;

                StringWriter sw = new StringWriter();
                try {
                    sourceTemplate.process(
                            NewLambdaJavaFunctionProjectWizardPageOne.this.dataModel.collectHandlerTemplateData(),
                            sw);
                    sw.flush();
                } catch (Exception e) {
                    LambdaPlugin.getDefault().reportException("Failed to generate handler source preview", e);
                }
                try {
                    sw.close();
                } catch (IOException ignored) {
                }

                String source = sw.toString();
                if (thisPage.sourcePreviewDocument != null) {
                    thisPage.sourcePreviewDocument.set(source);
                }
                if (thisPage.sourcePreview != null) {
                    thisPage.sourcePreview.getTextWidget().setRedraw(true);
                }
            }
        });
    }

    private void createShowReadmeFileCheckBox(Composite composite) {
        showReadmeFileCheckbox = newCheckbox(composite, "Show README guide after creating the project", 1);
    }

    private void bindControls() {

        handlerPackageTextObservable = SWTObservables.observeText(handlerPackageText, SWT.Modify);
        bindingContext.bindValue(handlerPackageTextObservable,
                PojoObservables.observeValue(dataModel, P_HANDLER_PACKAGE_NAME));

        handlerClassTextObservable = SWTObservables.observeText(handlerClassText, SWT.Modify);
        bindingContext.bindValue(handlerClassTextObservable,
                PojoObservables.observeValue(dataModel, P_HANDLER_CLASS_NAME));

        customHandlerInputTypeTextObservable = SWTObservables.observeText(customHandlerInputTypeText, SWT.Modify);
        bindingContext.bindValue(customHandlerInputTypeTextObservable,
                PojoObservables.observeValue(dataModel, P_CUSTOM_HANDLER_INPUT_TYPE));

        handlerOutputTypeTextObservable = SWTObservables.observeText(handlerOutputTypeText, SWT.Modify);
        bindingContext.bindValue(handlerOutputTypeTextObservable,
                PojoObservables.observeValue(dataModel, P_HANDLER_OUTPUT_TYPE));

        showReadmeFileCheckboxObservable = SWTObservables.observeSelection(showReadmeFileCheckbox);
        bindingContext.bindValue(showReadmeFileCheckboxObservable,
                PojoObservables.observeValue(dataModel, P_SHOW_README_FILE));

    }

    private void initializeValidators() {

        aggregateValidationStatus.addChangeListener(new IChangeListener() {
            public void handleChange(ChangeEvent arg0) {
                populateHandlerValidationStatus();
            }
        });

        ChainValidator<String> handlerPackageValidator = new ChainValidator<String>(handlerPackageTextObservable,
                new ValidPackageNameValidator("Please provide a valid package name for the handler class"));
        bindingContext.addValidationStatusProvider(handlerPackageValidator);
        new DecorationChangeListener(handlerPackageTextDecoration, handlerPackageValidator.getValidationStatus());

        ChainValidator<String> handlerClassValidator = new ChainValidator<String>(handlerClassTextObservable,
                new NotEmptyValidator("Please provide a valid class name for the handler"));
        bindingContext.addValidationStatusProvider(handlerClassValidator);
        new DecorationChangeListener(handlerClassTextDecoration, handlerClassValidator.getValidationStatus());

        ChainValidator<String> customHandlerInputChainValidator = new ChainValidator<String>(
                customHandlerInputTypeTextObservable, enableCustomHandlerInputTypeValidation, //enabler
                new NotEmptyValidator("Please provide a valid input type"));
        bindingContext.addValidationStatusProvider(customHandlerInputChainValidator);
        new DecorationChangeListener(customHandlerInputTypeTextDecoration,
                customHandlerInputChainValidator.getValidationStatus());

        ChainValidator<String> handlerOutputChainValidator = new ChainValidator<String>(
                handlerOutputTypeTextObservable, new NotEmptyValidator("Please provide a valid output type"));
        bindingContext.addValidationStatusProvider(handlerOutputChainValidator);
        new DecorationChangeListener(handlerOutputTypeTextDecoration,
                handlerOutputChainValidator.getValidationStatus());

        ChainValidator<Boolean> sdkInstalledValidator = new ChainValidator<Boolean>(sdkInstalledObservable,
                enableSdkInstalledValidation, // enabler
                new BooleanValidator("The selected input type requires the AWS Java SDK dependency. "
                        + "Please install the SDK first "
                        + "(Window -> Preference -> AWS Toolkit -> AWS SDK for Java) " + "and then retry."));
        bindingContext.addValidationStatusProvider(sdkInstalledValidator);
    }

    private void runHandlerValidators() {
        Iterator<?> iterator = bindingContext.getBindings().iterator();
        while (iterator.hasNext()) {
            Binding binding = (Binding) iterator.next();
            binding.updateTargetToModel();
        }
    }

    private void initializeDefaults() {
        handlerPackageTextObservable.setValue("");
        handlerClassTextObservable.setValue("LambdaFunctionHandler");
        customHandlerInputTypeTextObservable.setValue("Object");
        handlerOutputTypeTextObservable.setValue("Object");

        predefinedHandlerInputCombo.select(0);
        onPredefinedHandlerInputTypeComboSelectionChange();

        sdkInstalledObservable.setValue(checkSdkInstalled());

        showReadmeFileCheckboxObservable.setValue(LambdaPlugin.getDefault().getPreferenceStore()
                .getBoolean(LambdaPlugin.PREF_K_SHOW_README_AFTER_CREATE_NEW_PROJECT));
    }

    private boolean checkSdkInstalled() {
        return JavaSdkManager.getInstance().getDefaultSdkInstall() != null;
    }

    /**
     * @return returns the default class path entries, which includes all the
     *         default JRE entries plus the Lambda runtime API.
     */
    @Override
    public IClasspathEntry[] getDefaultClasspathEntries() {

        IClasspathEntry[] classpath = super.getDefaultClasspathEntries();

        classpath = addJunitLibrary(classpath);
        classpath = addLambdaRuntimeLibrary(classpath);

        if (dataModel.requireSdkDependency()) {
            classpath = addJavaSdkLibrary(classpath);
        }

        return classpath;
    }

    private IClasspathEntry[] addLambdaRuntimeLibrary(IClasspathEntry[] classpath) {
        IClasspathEntry[] augmentedClasspath = new IClasspathEntry[classpath.length + 1];
        System.arraycopy(classpath, 0, augmentedClasspath, 0, classpath.length);

        augmentedClasspath[classpath.length] = JavaCore.newContainerEntry(
                new LambdaRuntimeClasspathContainer(LambdaRuntimeLibraryManager.getInstance().getLatestVersion())
                        .getPath());

        return augmentedClasspath;
    }

    private IClasspathEntry[] addJavaSdkLibrary(IClasspathEntry[] classpath) {
        IClasspathEntry[] augmentedClasspath = new IClasspathEntry[classpath.length + 1];
        System.arraycopy(classpath, 0, augmentedClasspath, 0, classpath.length);

        augmentedClasspath[classpath.length] = JavaCore.newContainerEntry(
                new AwsClasspathContainer(JavaSdkManager.getInstance().getDefaultSdkInstall()).getPath());

        return augmentedClasspath;
    }

    private IClasspathEntry[] addJunitLibrary(IClasspathEntry[] classpath) {
        IClasspathEntry[] augmentedClasspath = new IClasspathEntry[classpath.length + 1];
        System.arraycopy(classpath, 0, augmentedClasspath, 0, classpath.length);

        final String JUNIT_CONTAINER_ID = "org.eclipse.jdt.junit.JUNIT_CONTAINER";
        augmentedClasspath[classpath.length] = JavaCore.newContainerEntry(new Path(JUNIT_CONTAINER_ID).append("4"));

        return augmentedClasspath;
    }

    /**
     * A very hacky way of combining the project name validation with our custom
     * validation logic.
     */
    @Override
    public void setPageComplete(boolean pageComplete) {
        isProjectNameValid = pageComplete;
        if (!pageComplete) {
            super.setPageComplete(pageComplete);
        } else {
            runHandlerValidators();
            populateHandlerValidationStatus();
        }
    }

    @Override
    public void setMessage(String newMessage, int newType) {
        super.setMessage(newMessage, newType);
        populateHandlerValidationStatus();
    }

    private void populateHandlerValidationStatus() {

        IStatus handlerInfoStatus = getHandlerInfoValidationStatus();
        if (handlerInfoStatus == null)
            return;

        boolean isHandlerInfoValid = (handlerInfoStatus.getSeverity() == IStatus.OK);

        if (isProjectNameValid && isHandlerInfoValid) {
            // always call super methods when handling our custom
            // validation status
            setErrorMessage(null);
            super.setPageComplete(true);
        } else {
            if (!isProjectNameValid) {
                setErrorMessage("Enter a valid project name");
            } else {
                setErrorMessage(handlerInfoStatus.getMessage());
            }
            super.setPageComplete(false);
        }
    }

    private IStatus getHandlerInfoValidationStatus() {
        if (aggregateValidationStatus == null) {
            return null;
        }

        Object value = aggregateValidationStatus.getValue();
        if (!(value instanceof IStatus))
            return null;
        return (IStatus) value;
    }

    private Font italicFont;

    private void setItalicFont(Control control) {
        FontData[] fontData = control.getFont().getFontData();
        for (FontData fd : fontData) {
            fd.setStyle(SWT.ITALIC);
        }
        italicFont = new Font(Display.getDefault(), fontData);
        control.setFont(italicFont);
    }

    @Override
    public void dispose() {
        if (italicFont != null)
            italicFont.dispose();
        super.dispose();
    }

}