/*
* Copyright 2012 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* 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.explorer.cloudformation.wizard;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
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.Text;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.databinding.NotEmptyValidator;
import com.amazonaws.services.cloudformation.model.TemplateParameter;
/**
* The second page in the create wizard second page
*/
public class CreateStackWizardSecondPage extends WizardPage {
private static final String ALLOWED_VALUES = "AllowedValues";
private static final String MAX_LENGTH = "MaxLength";
private static final String MIN_LENGTH = "MinLength";
private static final String MAX_VALUE = "MaxValue";
private static final String MIN_VALUE = "MinValue";
private static final String CONSTRAINT_DESCRIPTION = "ConstraintDescription";
private static final String ALLOWED_PATTERN = "AllowedPattern";
CreateStackWizard wizard;
private DataBindingContext bindingContext;
private boolean complete = false;
private static final String OK_MESSAGE = "Provide values for template parameters.";
private Composite comp;
private ScrolledComposite scrolledComp;
protected CreateStackWizardSecondPage(CreateStackWizard wizard) {
super("Fill in stack template parameters");
setMessage(OK_MESSAGE);
this.wizard = wizard;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets
* .Composite)
*/
public void createControl(final Composite parent) {
scrolledComp = new ScrolledComposite(parent, SWT.V_SCROLL);
scrolledComp.setExpandHorizontal(true);
scrolledComp.setExpandVertical(true);
GridDataFactory.fillDefaults().grab(true, true).applyTo(scrolledComp);
comp = new Composite(scrolledComp, SWT.None);
GridDataFactory.fillDefaults().grab(true, true).applyTo(comp);
GridLayoutFactory.fillDefaults().numColumns(2).applyTo(comp);
scrolledComp.setContent(comp);
scrolledComp.addControlListener(new ControlAdapter() {
public void controlResized(ControlEvent e) {
Rectangle r = scrolledComp.getClientArea();
scrolledComp.setMinSize(comp.computeSize(r.width, SWT.DEFAULT));
}
});
setControl(scrolledComp);
}
private void createContents() {
for ( Control c : comp.getChildren() ) {
c.dispose();
}
bindingContext = new DataBindingContext();
if (wizard.getDataModel().getTemplateParameters().isEmpty()) {
setComplete(true);
Label label = new Label(comp, SWT.NONE);
label.setText("Selected template has no parameters");
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false, 2, 1));
}
for (TemplateParameter param : wizard.getDataModel().getTemplateParameters()) {
createParameterSection(comp, param);
}
// Finally provide aggregate status reporting for the entire wizard page
final AggregateValidationStatus aggregateValidationStatus = new AggregateValidationStatus(bindingContext,
AggregateValidationStatus.MAX_SEVERITY);
aggregateValidationStatus.addChangeListener(new IChangeListener() {
public void handleChange(ChangeEvent event) {
Object value = aggregateValidationStatus.getValue();
if ( value instanceof IStatus == false )
return;
IStatus status = (IStatus) value;
if ( status.isOK() ) {
setErrorMessage(null);
setMessage(OK_MESSAGE, Status.OK);
} else if ( status.getSeverity() == Status.WARNING ) {
setErrorMessage(null);
setMessage(status.getMessage(), Status.WARNING);
} else if ( status.getSeverity() == Status.ERROR ) {
setErrorMessage(status.getMessage());
}
setComplete(status.isOK());
}
});
comp.layout();
Rectangle r = scrolledComp.getClientArea();
scrolledComp.setMinSize(comp.computeSize(r.width, SWT.DEFAULT));
}
@Override
public void setVisible(boolean visible) {
if ( visible ) {
createContents();
}
super.setVisible(true);
}
@Override
public boolean isPageComplete() {
return complete;
}
private void setComplete(boolean complete) {
this.complete = complete;
if ( getWizard().getContainer() != null )
getWizard().getContainer().updateButtons();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void createParameterSection(Composite comp, TemplateParameter param) {
Map parameterMap = (Map) wizard.getDataModel().getTemplate().get("Parameters");
// Unfortunately, we have to manually adjust for field decorations
FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault().getFieldDecoration(
FieldDecorationRegistry.DEC_ERROR);
int fieldDecorationWidth = fieldDecoration.getImage().getBounds().width;
Control paramControl = null;
ISWTObservableValue observeParameter = null;
ChainValidator<String> validationStatusProvider = null;
if ( parameterMap.containsKey(param.getParameterKey()) ) {
Label label = new Label(comp, SWT.None);
label.setText(param.getParameterKey());
Map paramMap = (Map) parameterMap.get(param.getParameterKey());
// Update the default value in the model.
if (wizard.getDataModel().getParameterValues().get(param.getParameterKey()) == null) {
wizard.getDataModel().getParameterValues().put(param.getParameterKey(), param.getDefaultValue());
}
// If the template enumerates allowed values, present them as a
// combo drop down
if ( paramMap.containsKey(ALLOWED_VALUES) ) {
Combo combo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
Collection<String> allowedValues = (Collection<String>) paramMap.get(ALLOWED_VALUES);
GridDataFactory.fillDefaults().grab(true, false).indent(fieldDecorationWidth, 0).applyTo(combo);
combo.setItems(allowedValues.toArray(new String[allowedValues.size()]));
observeParameter = SWTObservables.observeSelection(combo);
paramControl = combo;
} else {
// Otherwise, just use a text field with validation constraints
Text text = new Text(comp, SWT.BORDER);
GridDataFactory.fillDefaults().grab(true, false).indent(fieldDecorationWidth, 0).applyTo(text);
observeParameter = SWTObservables.observeText(text, SWT.Modify);
paramControl = text;
// Add validators for the constraints listed in the template
List<IValidator> validators = new ArrayList<IValidator>();
validators.add(new NotEmptyValidator("Please enter a value for " + param.getParameterKey()));
if ( paramMap.containsKey(ALLOWED_PATTERN) ) {
String pattern = (String) paramMap.get(ALLOWED_PATTERN);
Pattern p = Pattern.compile(pattern);
validators.add(new PatternValidator(p, param.getParameterKey() + ": "
+ (String) paramMap.get(CONSTRAINT_DESCRIPTION)));
}
if ( paramMap.containsKey(MIN_LENGTH) ) {
validators.add(new MinLengthValidator(Integer.parseInt((String) paramMap.get(MIN_LENGTH)), param
.getParameterKey()));
}
if ( paramMap.containsKey(MAX_LENGTH) ) {
validators.add(new MaxLengthValidator(Integer.parseInt((String) paramMap.get(MAX_LENGTH)), param
.getParameterKey()));
}
if ( paramMap.containsKey(MIN_VALUE) ) {
validators.add(new MinValueValidator(Integer.parseInt((String) paramMap.get(MIN_VALUE)), param
.getParameterKey()));
}
if ( paramMap.containsKey(MAX_VALUE) ) {
validators.add(new MaxValueValidator(Integer.parseInt((String) paramMap.get(MAX_VALUE)), param
.getParameterKey()));
}
if ( !validators.isEmpty() ) {
validationStatusProvider = new ChainValidator<String>(observeParameter,
validators.toArray(new IValidator[validators.size()]));
}
}
} else {
AwsToolkitCore.getDefault().logException("No parameter map object found for " + param.getParameterKey(),
null);
return;
}
if (param.getDescription() != null) {
Label description = new Label(comp, SWT.WRAP);
GridDataFactory.fillDefaults().grab(true, false).span(2, 1).indent(0, -8).applyTo(description);
description.setText(param.getDescription());
description.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
}
bindingContext.bindValue(observeParameter, Observables.observeMapEntry(wizard.getDataModel()
.getParameterValues(), param.getParameterKey(), String.class));
if ( validationStatusProvider != null ) {
bindingContext.addValidationStatusProvider(validationStatusProvider);
ControlDecoration decoration = new ControlDecoration(paramControl, SWT.TOP | SWT.LEFT);
decoration.setDescriptionText("Invalid value");
decoration.setImage(fieldDecoration.getImage());
new DecorationChangeListener(decoration, validationStatusProvider.getValidationStatus());
}
}
private final class PatternValidator implements IValidator {
private Pattern pattern;
private String errorMessage;
private PatternValidator(Pattern pattern, String errorMessage) {
this.pattern = pattern;
this.errorMessage = errorMessage;
}
public IStatus validate(Object value) {
Matcher matcher = pattern.matcher((CharSequence) value);
if ( matcher.matches() ) {
return ValidationStatus.ok();
} else {
return ValidationStatus.error(errorMessage);
}
}
}
private final class MaxLengthValidator implements IValidator {
private int maxLength;
private String fieldName;
private MaxLengthValidator(int maxLength, String fieldName) {
super();
this.maxLength = maxLength;
this.fieldName = fieldName;
}
public IStatus validate(Object value) {
if ( ((String) value).length() > maxLength ) {
return ValidationStatus.error(fieldName + " must be at most " + maxLength + " characters long");
} else {
return ValidationStatus.ok();
}
}
}
private final class MinLengthValidator implements IValidator {
private int minLength;
private String fieldName;
private MinLengthValidator(int minLength, String fieldName) {
super();
this.minLength = minLength;
this.fieldName = fieldName;
}
public IStatus validate(Object value) {
if ( ((String) value).length() < minLength ) {
return ValidationStatus.error(fieldName + " must be at least " + minLength + " characters long");
} else {
return ValidationStatus.ok();
}
}
}
private final class MinValueValidator implements IValidator {
private int minValue;
private String fieldName;
private MinValueValidator(int minValue, String fieldName) {
super();
this.minValue = minValue;
this.fieldName = fieldName;
}
public IStatus validate(Object value) {
String string = (String) value;
try {
if ( Integer.parseInt(string) < minValue ) {
return ValidationStatus.error(fieldName + " must be at least " + minValue);
} else {
return ValidationStatus.ok();
}
} catch ( Exception e ) {
return ValidationStatus.error(fieldName + " must be at least " + minValue);
}
}
}
private final class MaxValueValidator implements IValidator {
private int maxValue;
private String fieldName;
private MaxValueValidator(int maxValue, String fieldName) {
super();
this.maxValue = maxValue;
this.fieldName = fieldName;
}
public IStatus validate(Object value) {
String string = (String) value;
try {
if ( Integer.parseInt(string) > maxValue ) {
return ValidationStatus.error(fieldName + " must be at most " + maxValue);
} else {
return ValidationStatus.ok();
}
} catch ( Exception e ) {
return ValidationStatus.error(fieldName + " must be at most " + maxValue);
}
}
}
}