/* * Copyright 2011-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.dynamodb.editor; import static com.amazonaws.eclipse.dynamodb.editor.AttributeValueEditor.*; import static com.amazonaws.eclipse.dynamodb.editor.AttributeValueUtil.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.eclipse.jface.fieldassist.ContentProposal; import org.eclipse.jface.fieldassist.IContentProposal; import org.eclipse.jface.fieldassist.IContentProposalProvider; import org.eclipse.jface.fieldassist.TextContentAdapter; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; 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.Text; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.dynamodb.DynamoDBPlugin; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.ComparisonOperator; import com.amazonaws.services.dynamodbv2.model.Condition; /** * One row in the scan conditions editor. */ final class ScanConditionRow extends Composite { private static final ComparisonOperator[] COMPARISON_OPERATORS = new ComparisonOperator[] { ComparisonOperator.EQ, ComparisonOperator.NE, ComparisonOperator.GT, ComparisonOperator.GE, ComparisonOperator.LT, ComparisonOperator.LE, ComparisonOperator.BETWEEN, ComparisonOperator.BEGINS_WITH, ComparisonOperator.IN, ComparisonOperator.CONTAINS, ComparisonOperator.NOT_CONTAINS, ComparisonOperator.NULL, ComparisonOperator.NOT_NULL, }; private static final String[] COMPARISON_OPERATOR_STRINGS = new String[] { "Equals", "Not equals", "Greater than", "Greater than or equals", "Less than", "Less than or equals", "Between", "Begins with", "In", "Contains", "Does not contain", "Is null", "Is not null", }; private static final int EQ = 0; private String attributeName; private AttributeValue comparisonValue = new AttributeValue(); private int dataType = S; private ComparisonOperator comparisonOperator = ComparisonOperator.EQ; private final List<Control> conditionallyShownControls = new ArrayList<Control>(); private boolean enabled = true; private boolean valid = false; /* * Shared with the parent editor object, so must be carefully synchronized. */ private final Collection<String> knownAttributes; /** * Meaningful fields we have to track for fancy UI swapping. */ private Text singleValueEditor; private Text listValueEditor; private Button multiValueEditorButton; private Text betweenTextOne; private Label betweenTextLabel; private Text betweenTextTwo; private Button dataTypeButton; private Combo dataTypeCombo; /** * Returns the attribute name for the scan condition. */ public String getAttributeName() { return attributeName; } /** * Returns the scan condition represented by this row. */ public Condition getScanCondition() { Condition condition = new Condition().withComparisonOperator(comparisonOperator); switch (dataType) { case S: // fall through case SS: if ( comparisonOperator.equals(ComparisonOperator.BETWEEN) ) { // should only be two here condition.withAttributeValueList(new AttributeValue().withS(comparisonValue.getSS().get(0)), new AttributeValue().withS(comparisonValue.getSS().get(1))); } else if ( comparisonOperator.equals(ComparisonOperator.IN) ) { List<AttributeValue> attributeValues = new LinkedList<AttributeValue>(); for ( String value : comparisonValue.getSS() ) { attributeValues.add(new AttributeValue().withS(value)); } condition.withAttributeValueList(attributeValues); } else if ( comparisonOperator.equals(ComparisonOperator.NULL) || comparisonOperator.equals(ComparisonOperator.NOT_NULL) ) { // empty attribute value list } else { condition.withAttributeValueList(comparisonValue); } break; case N: // fall through case NS: if ( comparisonOperator.equals(ComparisonOperator.BETWEEN) ) { // should only be two here condition.withAttributeValueList(new AttributeValue().withN(comparisonValue.getNS().get(0)), new AttributeValue().withN(comparisonValue.getNS().get(1))); } else if ( comparisonOperator.equals(ComparisonOperator.IN) ) { List<AttributeValue> attributeValues = new LinkedList<AttributeValue>(); for ( String value : comparisonValue.getNS() ) { attributeValues.add(new AttributeValue().withN(value)); } condition.withAttributeValueList(attributeValues); } else if ( comparisonOperator.equals(ComparisonOperator.NULL) || comparisonOperator.equals(ComparisonOperator.NOT_NULL) ) { // empty attribute value list } else { condition.withAttributeValueList(comparisonValue); } break; default: throw new RuntimeException("Unrecognized data type " + dataType); } return condition; } public ScanConditionRow(final Composite parent, final Collection<String> knownAttributes) { super(parent, SWT.NONE); this.knownAttributes = knownAttributes; GridLayoutFactory.fillDefaults().numColumns(10).margins(5, 0).applyTo(this); GridDataFactory.fillDefaults().grab(true, false).applyTo(this); // Red X to remove control final Button removeCondition = new Button(this, SWT.PUSH); removeCondition.setToolTipText("Remove condition"); removeCondition.setImage(AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_REMOVE)); removeCondition.setText(""); removeCondition.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { parent.setRedraw(false); dispose(); parent.layout(true); parent.setRedraw(true); } }); // Check box for enablement of condition final Button enabledButton = new Button(this, SWT.CHECK); enabledButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { enabled = enabledButton.getSelection(); Control[] children = getChildren(); // Skip the first two controls, which are the remove button and // the enable checkbox. for ( int i = 2; i < children.length; i++ ) { children[i].setEnabled(enabled); } validate(); } }); enabledButton.setSelection(true); // Attribute name field final Label attributeNameLabel = new Label(this, SWT.None); attributeNameLabel.setText("Attribute:"); final Text attributeNameText = new Text(this, SWT.BORDER); attributeNameText.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create()); attributeNameText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { attributeName = attributeNameText.getText(); validate(); } }); setupAttributeNameContentAssist(attributeNameText); GridDataFactory.fillDefaults().grab(true, false).applyTo(attributeNameText); // Comparison selection combo final Combo comparison = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN); comparison.setItems(COMPARISON_OPERATOR_STRINGS); comparison.select(EQ); comparison.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { comparisonOperator = COMPARISON_OPERATORS[comparison.getSelectionIndex()]; configureComparisonEditorFields(); validate(); } }); singleValueEditor = new Text(this, SWT.BORDER); singleValueEditor.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { setAttribute(comparisonValue, Arrays.asList(singleValueEditor.getText()), dataTypeCombo.getSelectionIndex() == STRING ? S : N); } }); GridDataFactory.fillDefaults().grab(true, false).applyTo(singleValueEditor); conditionallyShownControls.add(singleValueEditor); listValueEditor = new Text(this, SWT.BORDER); listValueEditor.setEditable(false); listValueEditor.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { invokeMultiValueEditorDialog(); } }); GridDataFactory.fillDefaults().grab(true, false).applyTo(listValueEditor); conditionallyShownControls.add(listValueEditor); multiValueEditorButton = new Button(this, SWT.None); multiValueEditorButton.setText("..."); multiValueEditorButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { invokeMultiValueEditorDialog(); } }); GridDataFactory.swtDefaults().applyTo(multiValueEditorButton); conditionallyShownControls.add(multiValueEditorButton); betweenTextOne = new Text(this, SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(betweenTextOne); ModifyListener betweenModifyListener = new ModifyListener() { public void modifyText(ModifyEvent e) { setAttribute(comparisonValue, Arrays.asList(betweenTextOne.getText(), betweenTextTwo.getText()), dataTypeCombo.getSelectionIndex() == STRING ? SS : NS); } }; betweenTextOne.addModifyListener(betweenModifyListener); conditionallyShownControls.add(betweenTextOne); betweenTextLabel = new Label(this, SWT.READ_ONLY); betweenTextLabel.setText(" and "); GridDataFactory.swtDefaults().applyTo(betweenTextLabel); conditionallyShownControls.add(betweenTextLabel); betweenTextTwo = new Text(this, SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(betweenTextTwo); betweenTextTwo.addModifyListener(betweenModifyListener); conditionallyShownControls.add(betweenTextTwo); dataTypeButton = new Button(this, SWT.None); dataTypeButton.setImage(DynamoDBPlugin.getDefault().getImageRegistry().get(DynamoDBPlugin.IMAGE_A)); GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.TOP).grab(false, true).applyTo(dataTypeButton); dataTypeCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); dataTypeCombo.setItems(DATA_TYPE_ITEMS); dataTypeCombo.select(STRING); dataTypeCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Collection<String> values = getValuesFromAttribute(comparisonValue); switch (dataTypeCombo.getSelectionIndex()) { case STRING: if ( values.size() > 1 ) { dataType = SS; } else { dataType = S; } break; case NUMBER: if ( values.size() > 1 ) { dataType = NS; } else { dataType = N; } break; default: throw new RuntimeException("Unexpected selection index " + dataTypeCombo.getSelectionIndex()); } setAttribute(comparisonValue, values, dataType); } }); GridDataFactory.fillDefaults().align(SWT.RIGHT, SWT.TOP).grab(false, true).exclude(true).applyTo(dataTypeCombo); dataTypeCombo.setVisible(false); configureDataTypeControlSwap(dataTypeButton, dataTypeCombo, this); conditionallyShownControls.add(dataTypeButton); configureComparisonEditorFields(); } private void invokeMultiValueEditorDialog() { MultiValueAttributeEditorDialog multiValueEditorDialog = new MultiValueAttributeEditorDialog(Display .getDefault().getActiveShell(), comparisonValue, dataTypeCombo.getSelectionIndex()); int returnValue = multiValueEditorDialog.open(); if ( returnValue == 0 || returnValue == 1) { // Save set or single setAttribute(comparisonValue, multiValueEditorDialog.getValues(), dataTypeCombo.getSelectionIndex() == STRING ? SS : NS); listValueEditor.setText(format(comparisonValue)); } } /** * Configures the editor row based on the current comparison, hiding and * showing editor elements as necessary. */ private void configureComparisonEditorFields() { clearAttributes(comparisonValue); setRedraw(false); betweenTextOne.setText(""); betweenTextTwo.setText(""); listValueEditor.setText(""); singleValueEditor.setText(""); List<Control> toHide = new LinkedList<Control>(); toHide.addAll(conditionallyShownControls); toHide.add(dataTypeCombo); for ( Control c : toHide ) { c.setVisible(false); GridDataFactory.createFrom((GridData) c.getLayoutData()).exclude(true).applyTo(c); } Collection<Control> toShow = new LinkedList<Control>(); switch (comparisonOperator) { case BEGINS_WITH: toShow.add(singleValueEditor); dataType = S; break; case BETWEEN: toShow.add(betweenTextOne); toShow.add(betweenTextLabel); toShow.add(betweenTextTwo); toShow.add(dataTypeButton); switch (dataType) { case N: case NS: dataType = N; break; case S: case SS: dataType = S; break; } break; case IN: toShow.add(dataTypeButton); toShow.add(multiValueEditorButton); toShow.add(listValueEditor); switch (dataType) { case N: case NS: dataType = NS; break; case S: case SS: dataType = SS; break; } break; case EQ: case GE: case GT: case LE: case LT: case NE: case CONTAINS: case NOT_CONTAINS: toShow.add(dataTypeButton); toShow.add(singleValueEditor); switch (dataType) { case N: case NS: dataType = N; break; case S: case SS: dataType = S; break; } break; case NOT_NULL: case NULL: break; default: throw new RuntimeException("Unknown comparison " + comparisonOperator); } for ( Control c : toShow ) { c.setVisible(true); GridDataFactory.createFrom((GridData) c.getLayoutData()).exclude(false).applyTo(c); } layout(); setRedraw(true); } /** * Returns whether this scan condition should be included in the scan * request sent to DynamoDB */ public boolean shouldExecute() { return enabled && valid; } private void validate() { valid = true; } /** * Sets up content assist on the Text control given with the list of valid * completions for it. Returns the ContestAssist object. */ private ContentAssistCommandAdapter setupAttributeNameContentAssist(final Text text) { // Our simplified proposer doesn't quite work with a vanilla content // adapter implementation, so we have to tweak it a bit to get the // desired behavior. TextContentAdapter controlContentAdapter = new TextContentAdapter() { @Override public void insertControlContents(Control control, String value, int cursorPosition) { text.setText(value); } }; ContentAssistCommandAdapter assist = new ContentAssistCommandAdapter(text, controlContentAdapter, new StringContentProposalProvider(), null, null, true); // the assist adapter turns off auto-complete for normal typing, so turn // it back on assist.setAutoActivationCharacters(null); assist.setAutoActivationDelay(100); return assist; } /** * An IContentProposalProvider that deals in a set of strings */ final class StringContentProposalProvider implements IContentProposalProvider { public IContentProposal[] getProposals(String contents, int position) { synchronized (knownAttributes) { List<ContentProposal> list = new ArrayList<ContentProposal>(); String target = contents.trim().toLowerCase(); for ( String name : knownAttributes ) { if ( name.toLowerCase().startsWith(target) ) { list.add(new ContentProposal(name)); } } return list.toArray(new IContentProposal[list.size()]); } } } }