### Eclipse Workspace Patch 1.0 #P org.eclipse.jface.examples.databinding Index: META-INF/MANIFEST.MF =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.examples.databinding/META-INF/MANIFEST.MF,v retrieving revision 1.21 diff -u -r1.21 MANIFEST.MF --- META-INF/MANIFEST.MF 24 Aug 2009 21:33:42 -0000 1.21 +++ META-INF/MANIFEST.MF 30 Aug 2009 12:08:33 -0000 @@ -17,5 +17,6 @@ org.eclipse.jface.examples.databinding.mask.internal;x-internal:=true, org.eclipse.jface.examples.databinding.model;x-internal:=false, org.eclipse.jface.examples.databinding.radioGroup;x-internal:=false -Import-Package: com.ibm.icu.text +Import-Package: com.ibm.icu.math, + com.ibm.icu.text Bundle-RequiredExecutionEnvironment: J2SE-1.5 Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet035Editing.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/snippets/Snippet035Editing.java diff -N src/org/eclipse/jface/examples/databinding/snippets/Snippet035Editing.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/snippets/Snippet035Editing.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.snippets; + +import java.util.Calendar; +import java.util.Date; + +import org.eclipse.core.databinding.Binding; +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.databinding.editing.DateEditing; +import org.eclipse.core.databinding.editing.Editing; +import org.eclipse.core.databinding.editing.IntegerEditing; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.WritableList; +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.jface.databinding.swt.ISWTObservableValue; +import org.eclipse.jface.databinding.swt.SWTObservables; +import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; +import org.eclipse.jface.examples.databinding.util.EditingFactory; +import org.eclipse.jface.internal.databinding.provisional.fieldassist.ControlDecorationSupport; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.ListViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridLayout; +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.Shell; +import org.eclipse.swt.widgets.Text; + +public class Snippet035Editing { + + private DataBindingContext dbc; + + public static void main(String[] args) { + Display display = new Display(); + + Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { + public void run() { + Shell shell = new Snippet035Editing().createShell(); + Display display = Display.getCurrent(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + }); + } + + private Shell createShell() { + Display display = Display.getCurrent(); + Shell shell = new Shell(display); + shell.setText("Editing"); + shell.setLayout(new GridLayout(1, false)); + + dbc = new DataBindingContext(); + + createValueSection(shell); + createListSection(shell); + + shell.pack(); + shell.open(); + + return shell; + } + + private void createValueSection(Composite parent) { + Group section = createSectionGroup(parent, "Value bindings", false); + + // Edit an e-mail address. + Text emailText = createTextField(section, "E-Mail*"); + Editing emailEditing = EditingFactory.forEmailString(); + bindTextField(emailText, new WritableValue(), emailEditing); + + // Edit a required, positive integer and use the default validation message. + Text positiveText = createTextField(section, "Positive value*"); + IntegerEditing positiveEditing = EditingFactory.forInteger(); + positiveEditing.modelIntegerConstraints().required().positive(); + bindTextField(positiveText, new WritableValue(), positiveEditing); + + // Edit an integer within the range [1, 100] and use the default + // validation message. + Text rangeText = createTextField(section, "Value in [1, 100]"); + IntegerEditing rangeEditing = EditingFactory.forInteger(); + rangeEditing.modelIntegerConstraints().range(1, 100); + bindTextField(rangeText, new WritableValue(new Integer(0), null), rangeEditing); + + // Edit a percentage value which must lie within [0, 100] and use a custom + // validation message which indicates that the value actually represents + // a percentage value. + Text percentText = createTextField(section, "Value [%]"); + IntegerEditing percentEditing = EditingFactory.forInteger(); + percentEditing.modelIntegerConstraints().rangeMessage("Please specify a valid percentage value within [{0}, {1}].").range(0, 100); + bindTextField(percentText, new WritableValue(new Integer(-1), null), percentEditing); + + // Edit a hex integer within the range [0x00, 0xff]. The range validation + // message will display the range boundaries in hex format as well. + Text hexText = createTextField(section, "Hex value in [0x00, 0xff]"); + IntegerEditing hexEditing = EditingFactory.forHexInteger(2); + hexEditing.modelIntegerConstraints().range(0x00, 0xff); + bindTextField(hexText, new WritableValue(new Integer(0), null), hexEditing); + } + + private void createListSection(Composite parent) { + Group section = createSectionGroup(parent, "List bindings", true); + + // Our date should be >= 01.01.1990. + Calendar year1990Calendar = Calendar.getInstance(); + year1990Calendar.clear(); + year1990Calendar.set(1990, 0, 1); + Date year1990 = year1990Calendar.getTime(); + + // Edit a date supporting the default input/display patterns. + Text dateText = createTextField(section, "Date"); + DateEditing dateEditing = EditingFactory.forDate(); + dateEditing.modelDateConstraints().afterEqual(year1990); + final WritableValue dateObservable = new WritableValue(); + final Binding dateBinding = bindTextField(dateText, dateObservable, dateEditing); + + // Create a list to which the dates are added when the user hits ENTER. + new Label(section, SWT.LEFT); + ListViewer dateListViewer = new ListViewer(section); + GridDataFactory.fillDefaults().grab(true, true).hint(150, 200).applyTo(dateListViewer.getList()); + + dateListViewer.setContentProvider(new ObservableListContentProvider()); + dateListViewer.setLabelProvider(new LabelProvider()); + + // We use the same DateEditing object as for the text field above to + // create a list binding which maps the entered dates to their string + // representation which is then set as input on the ListViewer. + final WritableList targetDateList = new WritableList(); + final WritableList modelDateList = new WritableList(); + dbc.bindList( + targetDateList, + modelDateList, + dateEditing.createT2MListStrategy(), + dateEditing.createM2TListStrategy()); + + // Set the list containing the string representation of the dates as input. + dateListViewer.setInput(targetDateList); + + // Add the current date in the text field when the user hits ENTER. + dateText.addSelectionListener(new SelectionAdapter() { + public void widgetDefaultSelected(SelectionEvent e) { + IStatus dateValidationStatus = (IStatus) dateBinding.getValidationStatus().getValue(); + Date date = (Date) dateObservable.getValue(); + if (dateValidationStatus.isOK() && date != null) { + modelDateList.add(date); + } + } + }); + } + + private Binding bindTextField(Text text, IObservableValue modelValue, Editing editing) { + // Create the binding using the editing object. + ISWTObservableValue textObservable = SWTObservables.observeText(text, SWT.Modify); + Binding binding = dbc.bindValue( + textObservable, + modelValue, + editing.createT2MValueStrategy(), + editing.createM2TValueStrategy()); + + // Decorate the control with the validation status. + ControlDecorationSupport.create(binding, SWT.TOP); + + // Re-format when the text field looses the focus in order to always + // display the model in the default format in case multiple input formats + // are supported. + formatOnFocusOut(text, binding); + + return binding; + } + + private static void formatOnFocusOut(final Control control, final Binding binding) { + control.addFocusListener(new FocusAdapter() { + public void focusLost(FocusEvent e) { + IStatus dateValidationStatus = (IStatus) binding.getValidationStatus().getValue(); + if (dateValidationStatus.isOK()) { + binding.updateModelToTarget(); + } + } + }); + } + + private static Group createSectionGroup(Composite parent, String groupText, boolean grabVertical) { + Group section = new Group(parent, SWT.SHADOW_ETCHED_IN); + section.setText(groupText); + GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(5, 5).spacing(15, 5).applyTo(section); + GridDataFactory.fillDefaults().grab(true, grabVertical).applyTo(section); + return section; + } + + private static Text createTextField(Composite parent, String labelText) { + Label label = new Label(parent, SWT.LEFT); + label.setText(labelText); + GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(label); + + Text text = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, false).hint(150, SWT.DEFAULT).applyTo(text); + + return text; + } +} Index: src/org/eclipse/jface/examples/databinding/util/EditingFactory.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/util/EditingFactory.java diff -N src/org/eclipse/jface/examples/databinding/util/EditingFactory.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/util/EditingFactory.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.util; + +import org.eclipse.core.databinding.editing.DateEditing; +import org.eclipse.core.databinding.editing.IntegerEditing; +import org.eclipse.core.databinding.editing.StringEditing; + +import com.ibm.icu.text.DateFormat; +import com.ibm.icu.text.SimpleDateFormat; + +/** + * @since 3.2 + */ +public final class EditingFactory { + + private static final String REQUIRED_MESSAGE = "Please specify a value."; + + private static final String INTEGER_PARSE_ERROR_MESSAGE = "The input is invalid."; + + private static final String INTEGER_OUT_OF_RANGE_MESSAGE = "The value lies outside the supported range."; + + private static final String INTEGER_RANGE_MESSAGE = "The value must lie between {0} and {1}."; + + private static final String[] DATE_INPUT_PATTERNS = new String[] { + "dd.MM.yy", + "dd/MM/yy", + "dd.MM.yyyy", + "dd/MM/yyyy" + }; + + private static final String DATE_DISPLAY_PATTERN = "dd.MM.yyyy"; + + private static final String DATE_PARSE_ERROR_MESSAGE = createDateParseErrorMessage(DATE_INPUT_PATTERNS); + + private static final String EMAIL_REGEX = "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}\\b"; + + private static final String EMAIL_ERROR_MESSAGE = "Please specify a valid e-mail address."; + + private EditingFactory() { + // prevent instantiation + } + + public static StringEditing forString() { + return StringEditing.stripped(true); + } + + public static StringEditing forEmailString() { + StringEditing editing = StringEditing.stripped(true); + editing.modelStringConstraints() + .required() + .matchesMessage(EMAIL_ERROR_MESSAGE) + .matches(EMAIL_REGEX); + return editing; + } + + public static IntegerEditing forInteger() { + IntegerEditing editing = IntegerEditing.withDefaults( + INTEGER_PARSE_ERROR_MESSAGE, + INTEGER_OUT_OF_RANGE_MESSAGE); + configure(editing); + return editing; + } + + public static IntegerEditing forHexInteger(int digits) { + IntegerEditing editing = IntegerEditing.forFormat( + RadixNumberFormat.getHexInstance("0x", digits), + INTEGER_PARSE_ERROR_MESSAGE, + INTEGER_OUT_OF_RANGE_MESSAGE); + configure(editing); + return editing; + } + + private static void configure(IntegerEditing editing) { + editing.modelIntegerConstraints() + .requiredMessage(REQUIRED_MESSAGE) + .rangeMessage(INTEGER_RANGE_MESSAGE); + } + + public static DateEditing forDate() { + DateEditing editing = DateEditing + .forFormats( + createDateFormats(DATE_INPUT_PATTERNS), + DATE_PARSE_ERROR_MESSAGE, + new SimpleDateFormat(DATE_DISPLAY_PATTERN)); + editing.modelDateConstraints().requiredMessage(REQUIRED_MESSAGE); + return editing; + } + + private static DateFormat[] createDateFormats(String[] datePatterns) { + DateFormat[] dateFormats = new DateFormat[datePatterns.length]; + for (int i = 0; i < dateFormats.length; i++) { + dateFormats[i] = new SimpleDateFormat(datePatterns[i]); + } + return dateFormats; + } + + private static String createDateParseErrorMessage(String[] datePatterns) { + StringBuffer messageSb = new StringBuffer(); + messageSb.append("Supported formats: "); + for (int i = 0; i < datePatterns.length; i++) { + if (i > 0) { + messageSb.append(", "); + } + messageSb.append(datePatterns[i]); + } + return messageSb.toString(); + } +} Index: src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingSupport.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingSupport.java diff -N src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingSupport.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingSupport.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.util; + +import org.eclipse.core.databinding.editing.Editing; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.util.Policy; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.TextCellEditor; +import org.eclipse.swt.widgets.Composite; + +/** + * @since 3.2 + */ +public class ObservableMapEditingSupport extends EditingSupport { + + private final IObservableMap attributeMap; + + private final Editing editing; + + private final CellEditor cellEditor; + + public ObservableMapEditingSupport(ColumnViewer viewer, IObservableMap attributeMap, Editing editing) { + super(viewer); + this.attributeMap = attributeMap; + this.editing = editing; + this.cellEditor = new TextCellEditor((Composite) getViewer().getControl()); + } + + protected boolean canEdit(Object element) { + return true; + } + + protected CellEditor getCellEditor(Object element) { + return cellEditor; + } + + protected Object getValue(Object element) { + return editing.convertToTarget(attributeMap.get(element)); + } + + protected void setValue(Object element, Object value) { + MultiStatus validationStatus = new MultiStatus(Policy.JFACE_DATABINDING, 0, null, null); + Object modelValue = editing.convertToModel(value, validationStatus); + if (handleValidation(validationStatus)) { + attributeMap.put(element, modelValue); + } + } + + private boolean handleValidation(IStatus validationStatus) { + if (validationStatus.matches(IStatus.ERROR | IStatus.CANCEL)) { + MessageDialog.openError(getViewer().getControl().getShell(), "Validation Error", getValidationMessage(validationStatus)); + return false; + } + return true; + } + + private static String getValidationMessage(IStatus validationStatus) { + if (!validationStatus.isMultiStatus()) { + return validationStatus.getMessage(); + } + + MultiStatus multiStatus = (MultiStatus) validationStatus; + StringBuffer sb = new StringBuffer(); + IStatus[] childStatus = multiStatus.getChildren(); + for (int i = 0; i < childStatus.length; i++) { + if (i > 0) { + sb.append('\n'); + } + sb.append(getValidationMessage(childStatus[i])); + } + return sb.toString(); + } +} Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet036EditingTable.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/snippets/Snippet036EditingTable.java diff -N src/org/eclipse/jface/examples/databinding/snippets/Snippet036EditingTable.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/snippets/Snippet036EditingTable.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.snippets; + +import java.util.Date; + +import org.eclipse.core.databinding.Binding; +import org.eclipse.core.databinding.DataBindingContext; +import org.eclipse.core.databinding.beans.BeansObservables; +import org.eclipse.core.databinding.editing.DateEditing; +import org.eclipse.core.databinding.editing.Editing; +import org.eclipse.core.databinding.editing.IntegerEditing; +import org.eclipse.core.databinding.editing.StringEditing; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.databinding.swt.ISWTObservableValue; +import org.eclipse.jface.databinding.swt.SWTObservables; +import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; +import org.eclipse.jface.databinding.viewers.ViewersObservables; +import org.eclipse.jface.examples.databinding.ModelObject; +import org.eclipse.jface.examples.databinding.util.EditingFactory; +import org.eclipse.jface.examples.databinding.util.ObservableMapEditingCellLabelProvider; +import org.eclipse.jface.examples.databinding.util.ObservableMapEditingSupport; +import org.eclipse.jface.internal.databinding.provisional.fieldassist.ControlDecorationSupport; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.layout.GridLayout; +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.Shell; +import org.eclipse.swt.widgets.Text; + +public class Snippet036EditingTable { + + private final StringEditing nameEditing; + + private final IntegerEditing ageEditing; + + private final DateEditing bdayEditing; + + private DataBindingContext dbc; + + private TableViewer tableViewer; + + public static void main(String[] args) { + Display display = new Display(); + + Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { + public void run() { + Shell shell = new Snippet036EditingTable().createShell(); + Display display = Display.getCurrent(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + } + }); + } + + private Snippet036EditingTable() { + nameEditing = EditingFactory.forString(); + nameEditing.modelStringConstraints().required(); + + ageEditing = EditingFactory.forInteger(); + ageEditing.modelIntegerConstraints().required().nonNegative(); + + bdayEditing = EditingFactory.forDate(); + bdayEditing.modelDateConstraints().before(new Date()); + } + + private Shell createShell() { + Display display = Display.getCurrent(); + Shell shell = new Shell(display); + shell.setText("Editing"); + shell.setLayout(new GridLayout(2, false)); + + dbc = new DataBindingContext(); + + createTableSection(shell); + createFieldSection(shell); + + shell.pack(); + shell.open(); + + return shell; + } + + private void createTableSection(Composite parent) { + Group section = createSectionGroup(parent, 1); + + tableViewer = new TableViewer(section, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); + GridDataFactory.fillDefaults().grab(true, true).hint(350, 250).applyTo(tableViewer.getTable()); + tableViewer.getTable().setHeaderVisible(true); + tableViewer.getTable().setLinesVisible(true); + + ObservableListContentProvider contentProvider = new ObservableListContentProvider(); + tableViewer.setContentProvider(contentProvider); + IObservableSet contentElements = contentProvider.getKnownElements(); + + IObservableMap nameMap = BeansObservables.observeMap(contentElements, "name"); + createColumn("Name*", 150, nameMap, nameEditing); + + IObservableMap ageMap = BeansObservables.observeMap(contentElements, "age"); + createColumn("Age*", 50, ageMap, ageEditing); + + IObservableMap bdayMap = BeansObservables.observeMap(contentElements, "birthday"); + createColumn("Birthday", 80, bdayMap, bdayEditing); + + WritableList people = new WritableList(); + people.add(new Person("John Doe", 27)); + people.add(new Person("Steve Northover", 33)); + people.add(new Person("Grant Gayed", 54)); + people.add(new Person("Veronika Irvine", 25)); + people.add(new Person("Mike Wilson", 44)); + people.add(new Person("Christophe Cornu", 37)); + people.add(new Person("Lynne Kues", 65)); + people.add(new Person("Silenio Quarti", 15)); + + tableViewer.setInput(people); + + tableViewer.setSelection(new StructuredSelection(people.get(0))); + } + + private TableViewerColumn createColumn(String text, int width, IObservableMap attributeMap, Editing modelEditing) { + TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); + column.getColumn().setText(text); + column.getColumn().setWidth(width); + column.setLabelProvider(new ObservableMapEditingCellLabelProvider(attributeMap, modelEditing)); + column.setEditingSupport(new ObservableMapEditingSupport(tableViewer, attributeMap, modelEditing)); + return column; + } + + private void createFieldSection(Composite parent) { + final Group section = createSectionGroup(parent, 2); + + final IObservableValue personObservable = ViewersObservables.observeSingleSelection(tableViewer); + + Text nameText = createTextField(section, "Name*"); + IObservableValue nameObservable = BeansObservables.observeDetailValue(personObservable, "name", null); + bindTextField(nameText, nameObservable, nameEditing); + + Text ageText = createTextField(section, "Age*"); + IObservableValue ageObservable = BeansObservables.observeDetailValue(personObservable, "age", null); + bindTextField(ageText, ageObservable, ageEditing); + + Text bdayText = createTextField(section, "Birthday"); + IObservableValue bdayObservable = BeansObservables.observeDetailValue(personObservable, "birthday", null); + bindTextField(bdayText, bdayObservable, bdayEditing); + } + + private Binding bindTextField( + Text text, + IObservableValue modelValue, + Editing editing) { + // Create the binding using the editing object. + ISWTObservableValue textObservable = SWTObservables.observeText(text, SWT.Modify); + Binding binding = dbc.bindValue( + textObservable, + modelValue, + editing.createT2MValueStrategy(), + editing.createM2TValueStrategy()); + + // Decorate the control with the validation status. + ControlDecorationSupport.create(binding, SWT.TOP); + + formatOnFocusOut(text, binding); + + return binding; + } + + private static void formatOnFocusOut(final Control control, final Binding binding) { + control.addFocusListener(new FocusAdapter() { + public void focusLost(FocusEvent e) { + IStatus dateValidationStatus = (IStatus) binding.getValidationStatus().getValue(); + if (dateValidationStatus.isOK()) { + binding.updateModelToTarget(); + } + } + }); + } + + private Group createSectionGroup(Composite parent, int numColumns) { + Group section = new Group(parent, SWT.SHADOW_ETCHED_IN); + GridLayoutFactory.fillDefaults().numColumns(numColumns).equalWidth(false).margins(5, 5).spacing(15, 5).applyTo(section); + GridDataFactory.fillDefaults().grab(true, true).applyTo(section); + return section; + } + + private static Text createTextField(Composite parent, String labelText) { + Label label = new Label(parent, SWT.LEFT); + label.setText(labelText); + GridDataFactory.fillDefaults().align(SWT.LEFT, SWT.CENTER).applyTo(label); + + Text text = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, false).hint(200, SWT.DEFAULT).applyTo(text); + + return text; + } + + private static final class Person extends ModelObject { + + private String name; + + private int age; + + private Date birthday; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + firePropertyChange("name", this.name, this.name = name); + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + firePropertyChange("age", this.age, this.age = age); + } + + public Date getBirthday() { + return birthday; + } + + public void setBirthday(Date birthday) { + firePropertyChange("birthday", this.birthday, this.birthday = birthday); + } + } +} Index: src/org/eclipse/jface/examples/databinding/util/RadixNumberFormat.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/util/RadixNumberFormat.java diff -N src/org/eclipse/jface/examples/databinding/util/RadixNumberFormat.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/util/RadixNumberFormat.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.util; + +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.ParsePosition; + +import com.ibm.icu.math.BigDecimal; +import com.ibm.icu.text.NumberFormat; + +/** + * @since 3.2 + * + */ +public class RadixNumberFormat extends NumberFormat { + + private static final long serialVersionUID = 411884077848863891L; + + private final int radix; + + private final String prefix; + + private final int digits; + + private RadixNumberFormat(int radix, String prefix, int digits) { + this.radix = radix; + this.prefix = prefix != null ? prefix : ""; + this.digits = digits; + } + + public static RadixNumberFormat getHexInstance() { + return getHexInstance(null, 0); + } + + public static RadixNumberFormat getHexInstance(String prefix, int digits) { + return new RadixNumberFormat(16, prefix, digits); + } + + public StringBuffer format(double number, StringBuffer toAppendTo, + FieldPosition pos) { + // TODO omallo: Is this necessary due to a bug in NumberFormat.format(Object, ...)? + return format((long) number, toAppendTo, pos); + } + + public StringBuffer format(long number, StringBuffer toAppendTo, + FieldPosition pos) { + return toAppendTo.append(prefix).append(addPadding(Long.toString(number, radix))); + } + + public StringBuffer format(BigInteger number, StringBuffer toAppendTo, + FieldPosition pos) { + return toAppendTo.append(prefix).append(addPadding(number.toString(radix))); + } + + public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, + FieldPosition pos) { + throw new UnsupportedOperationException(); + } + + public Number parse(String text, ParsePosition parsePosition) { + if (text.length() == 0) { + return null; + } + + parsePosition.setIndex(parsePosition.getIndex() + text.length()); + + try { + if (text.startsWith(prefix)) { + return Integer.parseInt(text.substring(prefix.length()), radix); + } + return Integer.parseInt(text, radix); + } catch (NumberFormatException e) { + parsePosition.setErrorIndex(0); + return null; + } + } + + private String addPadding(String numberText) { + if (numberText.length() >= digits) { + return numberText; + } + + StringBuffer sb = new StringBuffer(); + for (int i = numberText.length(); i < digits; i++) { + sb.append('0'); + } + sb.append(numberText); + return sb.toString(); + } +} Index: src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingCellLabelProvider.java =================================================================== RCS file: src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingCellLabelProvider.java diff -N src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingCellLabelProvider.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/jface/examples/databinding/util/ObservableMapEditingCellLabelProvider.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.jface.examples.databinding.util; + +import org.eclipse.core.databinding.editing.Editing; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.jface.databinding.viewers.ObservableMapCellLabelProvider; +import org.eclipse.jface.viewers.ViewerCell; + +/** + * @since 3.2 + */ +public class ObservableMapEditingCellLabelProvider extends + ObservableMapCellLabelProvider { + + private final IObservableMap attributeMap; + + private final Editing editing; + + public ObservableMapEditingCellLabelProvider(IObservableMap attributeMap, Editing editing) { + super(attributeMap); + this.attributeMap = attributeMap; + this.editing = editing; + } + + public void update(ViewerCell cell) { + Object element = cell.getElement(); + Object attribute = attributeMap.get(element); + cell.setText((String) editing.convertToTarget(attribute)); + } +} #P org.eclipse.core.databinding Index: src/org/eclipse/core/internal/databinding/BindingMessages.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/BindingMessages.java,v retrieving revision 1.6 diff -u -r1.6 BindingMessages.java --- src/org/eclipse/core/internal/databinding/BindingMessages.java 9 May 2008 14:13:00 -0000 1.6 +++ src/org/eclipse/core/internal/databinding/BindingMessages.java 30 Aug 2009 12:08:35 -0000 @@ -18,7 +18,7 @@ /** * @since 1.0 - * + * */ public class BindingMessages { @@ -112,7 +112,7 @@ * Returns the resource object with the given key in the resource bundle for * JFace Data Binding. If there isn't any value under the given key, the key * is returned. - * + * * @param key * the resource name * @return the string @@ -128,16 +128,27 @@ /** * Returns a formatted string with the given key in the resource bundle for * JFace Data Binding. - * + * * @param key * @param arguments * @return formatted string, the key if the key is invalid */ - public static String formatString(String key, Object[] arguments) { + public static String getFormattedString(String key, Object[] arguments) { try { - return MessageFormat.format(bundle.getString(key), arguments); + return formatMessage(getString(key), arguments); } catch (MissingResourceException e) { return key; } } + + /** + * Formats the given message pattern with the provided arguments. + * + * @param pattern + * @param arguments + * @return formatted string + */ + public static String formatMessage(String pattern, Object[] arguments) { + return MessageFormat.format(pattern, arguments); + } } Index: src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java,v retrieving revision 1.2 diff -u -r1.2 StringToDateConverter.java --- src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java 25 May 2009 20:52:19 -0000 1.2 +++ src/org/eclipse/core/internal/databinding/conversion/StringToDateConverter.java 30 Aug 2009 12:08:35 -0000 @@ -15,13 +15,31 @@ import org.eclipse.core.databinding.conversion.IConverter; +import com.ibm.icu.text.DateFormat; /** * Convert a String to a java.util.Date, respecting the current locale * * @since 1.0 */ -public class StringToDateConverter extends DateConversionSupport implements IConverter { +public class StringToDateConverter extends DateConversionSupport implements + IConverter { + + /** + * + */ + public StringToDateConverter() { + super(); + } + + /** + * + * @param formats + */ + public StringToDateConverter(DateFormat[] formats) { + super(formats); + } + public Object convert(Object source) { return parse(source.toString()); } @@ -32,5 +50,5 @@ public Object getToType() { return Date.class; - } + } } Index: src/org/eclipse/core/internal/databinding/conversion/DateConversionSupport.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/DateConversionSupport.java,v retrieving revision 1.5 diff -u -r1.5 DateConversionSupport.java --- src/org/eclipse/core/internal/databinding/conversion/DateConversionSupport.java 25 May 2009 20:52:20 -0000 1.5 +++ src/org/eclipse/core/internal/databinding/conversion/DateConversionSupport.java 30 Aug 2009 12:08:35 -0000 @@ -31,58 +31,74 @@ *

*/ public abstract class DateConversionSupport { - private final static int DATE_FORMAT=DateFormat.SHORT; - private final static int DEFAULT_FORMATTER_INDEX=0; + private final static int DATE_FORMAT = DateFormat.SHORT; + private final static int DEFAULT_FORMATTER_INDEX = 0; - private final static int NUM_VIRTUAL_FORMATTERS=1; + private final static int NUM_VIRTUAL_FORMATTERS = 1; /** - * Alternative formatters for date, time and date/time. - * Raw milliseconds are covered as a special case. + * Alternative formatters for date, time and date/time. Raw milliseconds are + * covered as a special case. */ - // TODO: These could be shared, but would have to be synchronized. - private DateFormat[] formatters = { - new SimpleDateFormat(BindingMessages.getString(BindingMessages.DATE_FORMAT_DATE_TIME)), - new SimpleDateFormat(BindingMessages.getString(BindingMessages.DATEFORMAT_TIME)), - DateFormat.getDateTimeInstance(DATE_FORMAT, DateFormat.SHORT), - DateFormat.getDateInstance(DATE_FORMAT), - DateFormat.getTimeInstance(DateFormat.SHORT), - DateFormat.getDateTimeInstance(DATE_FORMAT,DateFormat.MEDIUM), - DateFormat.getTimeInstance(DateFormat.MEDIUM) - }; + private final DateFormat[] formatters; + + /** + * + */ + public DateConversionSupport() { + // TODO: These could be shared, but would have to be synchronized. + this(new DateFormat[] { + new SimpleDateFormat(BindingMessages + .getString(BindingMessages.DATE_FORMAT_DATE_TIME)), + new SimpleDateFormat(BindingMessages + .getString(BindingMessages.DATEFORMAT_TIME)), + DateFormat.getDateTimeInstance(DATE_FORMAT, DateFormat.SHORT), + DateFormat.getDateInstance(DATE_FORMAT), + DateFormat.getTimeInstance(DateFormat.SHORT), + DateFormat.getDateTimeInstance(DATE_FORMAT, DateFormat.MEDIUM), + DateFormat.getTimeInstance(DateFormat.MEDIUM) }); + } + + /** + * + * @param formats + */ + public DateConversionSupport(DateFormat[] formats) { + this.formatters = formats; + } /** * Tries all available formatters to parse the given string according to the * default locale or as a raw millisecond value and returns the result of the * first successful run. - * + * * @param str A string specifying a date according to the default locale or in raw milliseconds * @return The parsed date, or null, if no available formatter could interpret the input string */ protected Date parse(String str) { for (int formatterIdx = 0; formatterIdx < formatters.length; formatterIdx++) { - Date parsed=parse(str,formatterIdx); - if(parsed!=null) { + Date parsed = parse(str, formatterIdx); + if (parsed != null) { return parsed; } } return null; } - protected Date parse(String str,int formatterIdx) { - if(formatterIdx>=0) { - ParsePosition pos=new ParsePosition(0); - if (str == null) { - return null; - } - Date date=formatters[formatterIdx].parse(str,pos); - if(pos.getErrorIndex()!=-1||pos.getIndex()!=str.length()) { - return null; - } - return date; + protected Date parse(String str, int formatterIdx) { + if (formatterIdx >= 0) { + ParsePosition pos = new ParsePosition(0); + if (str == null) { + return null; + } + Date date = formatters[formatterIdx].parse(str, pos); + if (pos.getErrorIndex() != -1 || pos.getIndex() != str.length()) { + return null; + } + return date; } try { - long millisecs=Long.parseLong(str); + long millisecs = Long.parseLong(str); return new Date(millisecs); } catch(NumberFormatException exc) { @@ -96,20 +112,20 @@ * @return a string representation of the given date according to the default locale */ protected String format(Date date) { - return format(date,DEFAULT_FORMATTER_INDEX); + return format(date, DEFAULT_FORMATTER_INDEX); } - protected String format(Date date,int formatterIdx) { + protected String format(Date date, int formatterIdx) { if (date == null) return null; - if(formatterIdx>=0) { + if (formatterIdx >= 0) { return formatters[formatterIdx].format(date); } return String.valueOf(date.getTime()); } protected int numFormatters() { - return formatters.length+NUM_VIRTUAL_FORMATTERS; + return formatters.length + NUM_VIRTUAL_FORMATTERS; } /** @@ -118,7 +134,7 @@ * This is for testing purposes only and should not be a part of the API if * this class was to be exposed. *

- * + * * @param index * @return date format */ Index: src/org/eclipse/core/internal/databinding/conversion/StringToNumberParser.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/StringToNumberParser.java,v retrieving revision 1.6 diff -u -r1.6 StringToNumberParser.java --- src/org/eclipse/core/internal/databinding/conversion/StringToNumberParser.java 28 Oct 2008 19:54:39 -0000 1.6 +++ src/org/eclipse/core/internal/databinding/conversion/StringToNumberParser.java 30 Aug 2009 12:08:35 -0000 @@ -21,7 +21,7 @@ /** * Utility class for the parsing of strings to numbers. - * + * * @since 1.0 */ public class StringToNumberParser { @@ -73,7 +73,7 @@ /** * The result of a parse operation. - * + * * @since 1.0 */ public static class ParseResult { @@ -84,7 +84,7 @@ * The number as a result of the conversion. null if the * value could not be converted or if the type is not a primitive and * the value was an empty string. - * + * * @return number */ public Number getNumber() { @@ -94,7 +94,7 @@ /** * ParsePosition if an error occurred while parsing. null * if no error occurred. - * + * * @return parse position */ public ParsePosition getPosition() { @@ -104,7 +104,7 @@ /** * Formats an appropriate message for a parsing error. - * + * * @param value * @param position * @return message @@ -115,17 +115,17 @@ .getErrorIndex() : position.getIndex(); if (errorIndex < value.length()) { - return BindingMessages.formatString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR, + return BindingMessages.getFormattedString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR, new Object[] { value, new Integer(errorIndex + 1), new Character(value.charAt(errorIndex)) }); } - return BindingMessages.formatString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR_NO_CHARACTER, + return BindingMessages.getFormattedString(BindingMessages.VALIDATE_NUMBER_PARSE_ERROR_NO_CHARACTER, new Object[] { value, new Integer(errorIndex + 1) }); } /** * Formats an appropriate message for an out of range error. - * + * * @param minValue * @param maxValue * @param numberFormat when accessed method synchronizes on instance @@ -133,6 +133,22 @@ */ public static String createOutOfRangeMessage(Number minValue, Number maxValue, NumberFormat numberFormat) { + return createOutOfRangeMessage( + BindingMessages.getString("Validate_NumberOutOfRangeError"), minValue, maxValue, numberFormat); //$NON-NLS-1$ + } + + /** + * Formats an appropriate message for an out of range error. + * + * @param message + * @param minValue + * @param maxValue + * @param numberFormat + * when accessed method synchronizes on instance + * @return message + */ + public static String createOutOfRangeMessage(String message, + Number minValue, Number maxValue, NumberFormat numberFormat) { String min = null; String max = null; @@ -141,14 +157,14 @@ max = numberFormat.format(maxValue); } - return BindingMessages.formatString( - "Validate_NumberOutOfRangeError", new Object[] { min, max }); //$NON-NLS-1$ + return BindingMessages.formatMessage(message, new Object[] { min, + max }); } /** * Returns true if the provided number is in * the range of a integer. - * + * * @param number * @return true if a valid integer * @throws IllegalArgumentException @@ -160,7 +176,7 @@ /** * Validates the range of the provided number. - * + * * @param number * @param bitLength number of bits allowed to be in range * @return true if in range @@ -204,7 +220,7 @@ /** * Returns true if the provided number is in * the range of a long. - * + * * @param number * @return true if in range * @throws IllegalArgumentException @@ -217,7 +233,7 @@ /** * Returns true if the provided number is in * the range of a float. - * + * * @param number * @return true if in range * @throws IllegalArgumentException @@ -267,7 +283,7 @@ /** * Returns true if the provided number is in * the range of a double. - * + * * @param number * @return true if in range * @throws IllegalArgumentException @@ -280,7 +296,7 @@ /** * Returns true if the provided number is in * the range of a short. - * + * * @param number * @return true if in range */ @@ -291,7 +307,7 @@ /** * Returns true if the provided number is in * the range of a byte. - * + * * @param number * @return true if in range */ Index: src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java,v retrieving revision 1.2 diff -u -r1.2 DateToStringConverter.java --- src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java 25 May 2009 20:52:19 -0000 1.2 +++ src/org/eclipse/core/internal/databinding/conversion/DateToStringConverter.java 30 Aug 2009 12:08:35 -0000 @@ -15,17 +15,35 @@ import org.eclipse.core.databinding.conversion.IConverter; +import com.ibm.icu.text.DateFormat; /** - * Converts a Java.util.Date to a String using the current locale. Null date + * Converts a Java.util.Date to a String using the current locale. Null date * values are converted to an empty string. * * @since 1.0 */ -public class DateToStringConverter extends DateConversionSupport implements IConverter { +public class DateToStringConverter extends DateConversionSupport implements + IConverter { + + /** + * + */ + public DateToStringConverter() { + super(); + } + + /** + * + * @param format + */ + public DateToStringConverter(DateFormat format) { + super(new DateFormat[] { format }); + } + public Object convert(Object source) { if (source != null) - return format((Date)source); + return format((Date) source); return ""; //$NON-NLS-1$ } @@ -35,5 +53,5 @@ public Object getToType() { return String.class; - } + } } Index: src/org/eclipse/core/internal/databinding/validation/StringToIntegerValidator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/validation/StringToIntegerValidator.java,v retrieving revision 1.2 diff -u -r1.2 StringToIntegerValidator.java --- src/org/eclipse/core/internal/databinding/validation/StringToIntegerValidator.java 1 Apr 2007 20:58:13 -0000 1.2 +++ src/org/eclipse/core/internal/databinding/validation/StringToIntegerValidator.java 30 Aug 2009 12:08:35 -0000 @@ -13,7 +13,6 @@ import org.eclipse.core.internal.databinding.conversion.StringToNumberParser; - /** * Validates that a string is of the appropriate format and is in the range of * an integer. @@ -31,6 +30,16 @@ super(converter, MIN, MAX); } + /** + * @param converter + * @param parseErrorMessage + * @param outOfRangeMessage + */ + public StringToIntegerValidator(NumberFormatConverter converter, + String parseErrorMessage, String outOfRangeMessage) { + super(converter, MIN, MAX, parseErrorMessage, outOfRangeMessage); + } + /* (non-Javadoc) * @see org.eclipse.core.internal.databinding.validation.AbstractStringToNumberValidator#inRange(java.lang.Number) */ Index: src/org/eclipse/core/internal/databinding/validation/StringToDateValidator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/validation/StringToDateValidator.java,v retrieving revision 1.5 diff -u -r1.5 StringToDateValidator.java --- src/org/eclipse/core/internal/databinding/validation/StringToDateValidator.java 9 May 2008 14:13:00 -0000 1.5 +++ src/org/eclipse/core/internal/databinding/validation/StringToDateValidator.java 30 Aug 2009 12:08:35 -0000 @@ -28,24 +28,36 @@ public class StringToDateValidator implements IValidator { private final StringToDateConverter converter; + private final String parseErrorMessage; + /** * @param converter */ public StringToDateValidator(StringToDateConverter converter) { + this(converter, null); + } + + /** + * @param converter + * @param parseErrorMessage + */ + public StringToDateValidator(StringToDateConverter converter, + String parseErrorMessage) { this.converter = converter; + this.parseErrorMessage = parseErrorMessage; } /* * (non-Javadoc) - * + * * @see org.eclipse.core.databinding.validation.IValidator#validate(java.lang.Object) */ public IStatus validate(Object value) { - if (value instanceof String && ((String)value).trim().length()==0) { + if (value instanceof String && ((String) value).trim().length() == 0) { return Status.OK_STATUS; } Object convertedValue = converter.convert(value); - //The StringToDateConverter returns null if it can't parse the date. + // The StringToDateConverter returns null if it can't parse the date. if (convertedValue == null) { return ValidationStatus.error(getErrorMessage()); } @@ -55,10 +67,14 @@ /* * (non-Javadoc) - * + * * @see org.eclipse.core.internal.databinding.validation.WrappedConverterValidator#getErrorMessage() */ protected String getErrorMessage() { + if (parseErrorMessage != null) { + return parseErrorMessage; + } + Date sampleDate = new Date(); // FIXME We need to use the information from the @@ -79,7 +95,7 @@ private static class FormatUtil extends DateConversionSupport { /* * (non-Javadoc) - * + * * @see org.eclipse.core.internal.databinding.conversion.DateConversionSupport#numFormatters() */ protected int numFormatters() { @@ -88,7 +104,7 @@ /* * (non-Javadoc) - * + * * @see org.eclipse.core.internal.databinding.conversion.DateConversionSupport#format(java.util.Date) */ protected String format(Date date) { @@ -97,7 +113,7 @@ /* * (non-Javadoc) - * + * * @see org.eclipse.core.internal.databinding.conversion.DateConversionSupport#format(java.util.Date, * int) */ Index: src/org/eclipse/core/internal/databinding/validation/AbstractStringToNumberValidator.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/validation/AbstractStringToNumberValidator.java,v retrieving revision 1.2 diff -u -r1.2 AbstractStringToNumberValidator.java --- src/org/eclipse/core/internal/databinding/validation/AbstractStringToNumberValidator.java 4 Jul 2007 20:08:45 -0000 1.2 +++ src/org/eclipse/core/internal/databinding/validation/AbstractStringToNumberValidator.java 30 Aug 2009 12:08:35 -0000 @@ -11,6 +11,8 @@ package org.eclipse.core.internal.databinding.validation; +import java.text.ParsePosition; + import org.eclipse.core.databinding.validation.IValidator; import org.eclipse.core.databinding.validation.ValidationStatus; import org.eclipse.core.internal.databinding.conversion.StringToNumberParser; @@ -31,7 +33,10 @@ private final Number min; private final Number max; - private String outOfRangeMessage; + private final String parseErrorMessage; + private final String outOfRangeMessage; + + private String formattedOutOfRangeMessage; /** * Constructs a new instance. @@ -42,9 +47,26 @@ */ protected AbstractStringToNumberValidator(NumberFormatConverter converter, Number min, Number max) { + this(converter, min, max, null, null); + } + + /** + * Constructs a new instance. + * + * @param converter converter and thus formatter to be used in validation + * @param min minimum value, used for reporting a range error to the user + * @param max maximum value, used for reporting a range error to the user + * @param parseErrorMessage + * @param outOfRangeMessage + */ + protected AbstractStringToNumberValidator(NumberFormatConverter converter, + Number min, Number max, String parseErrorMessage, + String outOfRangeMessage) { this.converter = converter; this.min = min; this.max = max; + this.parseErrorMessage = parseErrorMessage; + this.outOfRangeMessage = outOfRangeMessage; if (converter.getToType() instanceof Class) { Class clazz = (Class) converter.getToType(); @@ -69,17 +91,15 @@ if (result.getNumber() != null) { if (!isInRange(result.getNumber())) { - if (outOfRangeMessage == null) { - outOfRangeMessage = StringToNumberParser - .createOutOfRangeMessage(min, max, converter - .getNumberFormat()); + if (formattedOutOfRangeMessage == null) { + formattedOutOfRangeMessage = createOutOfRangeMessage(); } - return ValidationStatus.error(outOfRangeMessage); + return ValidationStatus.error(formattedOutOfRangeMessage); } } else if (result.getPosition() != null) { - String parseErrorMessage = StringToNumberParser.createParseErrorMessage( - (String) value, result.getPosition()); + String parseErrorMessage = createParseErrorMessage((String) value, + result.getPosition()); return ValidationStatus.error(parseErrorMessage); } @@ -94,4 +114,22 @@ * @return true if in range */ protected abstract boolean isInRange(Number number); + + private String createParseErrorMessage(String input, + ParsePosition parsePosition) { + if (parseErrorMessage == null) { + return StringToNumberParser.createParseErrorMessage(input, + parsePosition); + } + return parseErrorMessage; + } + + private String createOutOfRangeMessage() { + if (outOfRangeMessage == null) { + return StringToNumberParser.createOutOfRangeMessage(min, max, + converter.getNumberFormat()); + } + return StringToNumberParser.createOutOfRangeMessage(outOfRangeMessage, + min, max, converter.getNumberFormat()); + } } Index: META-INF/MANIFEST.MF =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/META-INF/MANIFEST.MF,v retrieving revision 1.21 diff -u -r1.21 MANIFEST.MF --- META-INF/MANIFEST.MF 25 Aug 2009 04:57:27 -0000 1.21 +++ META-INF/MANIFEST.MF 30 Aug 2009 12:08:35 -0000 @@ -8,7 +8,9 @@ Bundle-Localization: plugin Export-Package: org.eclipse.core.databinding, org.eclipse.core.databinding.conversion;x-internal:=false, + org.eclipse.core.databinding.editing, org.eclipse.core.databinding.validation;x-internal:=false, + org.eclipse.core.databinding.validation.constraint, org.eclipse.core.internal.databinding;x-friends:="org.eclipse.core.databinding.beans", org.eclipse.core.internal.databinding.conversion;x-friends:="org.eclipse.jface.tests.databinding", org.eclipse.core.internal.databinding.validation;x-friends:="org.eclipse.jface.tests.databinding" Index: src/org/eclipse/core/databinding/editing/StringEditing.java =================================================================== RCS file: src/org/eclipse/core/databinding/editing/StringEditing.java diff -N src/org/eclipse/core/databinding/editing/StringEditing.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/editing/StringEditing.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.editing; + +import org.eclipse.core.databinding.conversion.IConverter; +import org.eclipse.core.databinding.validation.constraint.StringConstraints; +import org.eclipse.core.internal.databinding.conversion.StringStripConverter; + +/** + * @since 1.3 + */ +public final class StringEditing extends Editing { + + private StringEditing(IConverter targetConverter) { + super(new StringConstraints(), new StringConstraints(), + new StringConstraints()); + + setTargetConverter(targetConverter); + } + + /** + * Creates a new editing object for strings which performs no validation or + * conversion. + * + * @return The new editing object which performs no validation or + * conversion. + */ + public static StringEditing withDefaults() { + return new StringEditing(null); + } + + /** + * Creates a new editing object which strips whitespace from both ends of + * the input string. + * + * @param stripToNull + * Whether to convert the input string to null in + * case stripping the input string results in an empty string. + * @return The new editing object which strips whitespace from both ends of + * the input string. + */ + public static StringEditing stripped(boolean stripToNull) { + return new StringEditing(new StringStripConverter(stripToNull)); + } + + /** + * Returns the target constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link StringConstraints + * string target constraints} of this editing object and is equivalent to + * {@code (StringConstraints) targetConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #targetConstraints() + * @see StringConstraints + */ + public StringConstraints targetStringConstraints() { + return (StringConstraints) targetConstraints(); + } + + /** + * Returns the model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link StringConstraints + * string model constraints} of this editing object and is equivalent to + * {@code (StringConstraints) modelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #modelConstraints() + * @see StringConstraints + */ + public StringConstraints modelStringConstraints() { + return (StringConstraints) modelConstraints(); + } + + /** + * Returns the before-set model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link StringConstraints + * string before-set model constraints} of this editing object and is + * equivalent to {@code (StringConstraints) beforeSetModelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #beforeSetModelConstraints() + * @see StringConstraints + */ + public StringConstraints beforeSetModelStringConstraints() { + return (StringConstraints) beforeSetModelConstraints(); + } +} Index: src/org/eclipse/core/databinding/validation/constraint/Constraints.java =================================================================== RCS file: src/org/eclipse/core/databinding/validation/constraint/Constraints.java diff -N src/org/eclipse/core/databinding/validation/constraint/Constraints.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/validation/constraint/Constraints.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.validation.constraint; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.databinding.util.Policy; +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.internal.databinding.BindingMessages; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; + +/** + * @since 1.3 + */ +public class Constraints { + + /** + * Constant denoting an aggregation strategy that merges multiple non-OK + * status objects in a {@link MultiStatus}. Returns an OK status result if + * all statuses from the set of validators to apply are an OK status. + * Returns a single status if there is only one non-OK status. + * + * @see #aggregationPolicy(int) + */ + public static final int AGGREGATION_MERGED = 1; + + /** + * Constant denoting an aggregation strategy that always returns the most + * severe status from the set of validators to apply. If there is more than + * one status at the same severity level, it picks the first one it + * encounters. + * + * @see #aggregationPolicy(int) + */ + public static final int AGGREGATION_MAX_SEVERITY = 2; + + private final List validators = new ArrayList(); + + private ConstraintsValidator cachedValidator = null; + + private int aggregationPolicy = AGGREGATION_MAX_SEVERITY; + + /** + * Adds a custom validator to the set of constraints to apply. + * + * @param validator + * The custom validator to add to the set of constraints. + * @return This constraints instance for method chaining. + */ + public final Constraints addValidator(IValidator validator) { + validators.add(validator); + cachedValidator = null; + return this; + } + + /** + * Sets the aggregation policy to apply to the individual validations which + * constitute this set of constraints. + * + * @param policy + * The validation aggregation policy to set. Must be one of + * {@link #AGGREGATION_MERGED} or + * {@link #AGGREGATION_MAX_SEVERITY}. + * @return This constraints instance for method chaining. + * + * @see #AGGREGATION_MERGED + * @see #AGGREGATION_MAX_SEVERITY + */ + public final Constraints aggregationPolicy(int policy) { + this.aggregationPolicy = policy; + return this; + } + + /** + * Creates a validator which enforces the current set of constraints. + * + *

+ * Note that this method will return null in case the set of + * constraints to apply is empty. + *

+ * + * @return A validator which enforces the current set of constraints. May be + * null. + */ + public final IValidator createValidator() { + if (!validators.isEmpty()) { + if (cachedValidator == null) { + IValidator[] currentValidators = (IValidator[]) validators + .toArray(new IValidator[validators.size()]); + cachedValidator = new ConstraintsValidator(currentValidators, + aggregationPolicy); + } + return cachedValidator; + } + return null; + } + + private static final class ConstraintsValidator implements IValidator { + + private final IValidator[] validators; + + private final int aggregationPolicy; + + public ConstraintsValidator(IValidator[] validators, + int aggregationPolicy) { + this.validators = validators; + this.aggregationPolicy = aggregationPolicy; + } + + public IStatus validate(Object value) { + return (aggregationPolicy == AGGREGATION_MERGED) ? validateMerged(value) + : validateMaxSeverity(value); + } + + private IStatus validateMerged(Object value) { + List statuses = new ArrayList(); + for (int i = 0; i < validators.length; i++) { + IValidator validator = validators[i]; + IStatus status = validator.validate(value); + if (!status.isOK()) { + statuses.add(status); + } + } + + if (statuses.size() == 1) { + return (IStatus) statuses.get(0); + } + + if (!statuses.isEmpty()) { + MultiStatus result = new MultiStatus(Policy.JFACE_DATABINDING, + 0, BindingMessages + .getString(BindingMessages.MULTIPLE_PROBLEMS), + null); + for (Iterator it = statuses.iterator(); it.hasNext();) { + IStatus status = (IStatus) it.next(); + result.merge(status); + } + return result; + } + + return Status.OK_STATUS; + } + + private IStatus validateMaxSeverity(Object value) { + int maxSeverity = IStatus.OK; + IStatus maxStatus = Status.OK_STATUS; + for (int i = 0; i < validators.length; i++) { + IValidator validator = validators[i]; + IStatus status = validator.validate(value); + if (status.getSeverity() > maxSeverity) { + maxSeverity = status.getSeverity(); + maxStatus = status; + } + } + return maxStatus; + } + } +} Index: src/org/eclipse/core/databinding/editing/Editing.java =================================================================== RCS file: src/org/eclipse/core/databinding/editing/Editing.java diff -N src/org/eclipse/core/databinding/editing/Editing.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/editing/Editing.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,440 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.editing; + +import org.eclipse.core.databinding.UpdateListStrategy; +import org.eclipse.core.databinding.UpdateSetStrategy; +import org.eclipse.core.databinding.UpdateValueStrategy; +import org.eclipse.core.databinding.conversion.IConverter; +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.constraint.Constraints; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; + +/** + * @since 1.3 + */ +public class Editing { + + private final Constraints targetConstraints; + + private final Constraints modelConstraints; + + private final Constraints beforeSetModelConstraints; + + private IConverter targetConverter; + + private IConverter modelConverter; + + /** + * + */ + public Editing() { + this(new Constraints(), new Constraints(), new Constraints()); + } + + protected Editing(Constraints targetConstraints, + Constraints modelConstraints, Constraints beforeSetModelConstraints) { + this.targetConstraints = targetConstraints; + this.modelConstraints = modelConstraints; + this.beforeSetModelConstraints = beforeSetModelConstraints; + } + + /** + * Returns the target constraints to apply. + * + * @return The target constraints to apply. + */ + public final Constraints targetConstraints() { + return targetConstraints; + } + + /** + * Returns the model constraints to apply. + * + * @return The model constraints to apply. + */ + public final Constraints modelConstraints() { + return modelConstraints; + } + + /** + * Returns the before-set model constraints to apply. + * + * @return The before-set model constraints to apply. + */ + public final Constraints beforeSetModelConstraints() { + return beforeSetModelConstraints; + } + + protected final void setTargetConverter(IConverter converter) { + this.targetConverter = converter; + } + + protected final void setModelConverter(IConverter converter) { + this.modelConverter = converter; + } + + /** + * Creates a new target-to-model {@link UpdateValueStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new target-to-model {@link UpdateValueStrategy} configured by + * the current state of this editing object. + * + * @see UpdateValueStrategy#UpdateValueStrategy() + */ + public UpdateValueStrategy createT2MValueStrategy() { + return applyToT2MValueStrategy(new UpdateValueStrategy()); + } + + /** + * Creates a new target-to-model {@link UpdateValueStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new target-to-model {@link UpdateValueStrategy} configured by + * the current state of this editing object. + * + * @see UpdateValueStrategy#UpdateValueStrategy(int) + */ + public UpdateValueStrategy createT2MValueStrategy(int updatePolicy) { + return applyToT2MValueStrategy(new UpdateValueStrategy(updatePolicy)); + } + + /** + * Creates a new model-to-target {@link UpdateValueStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new model-to-target {@link UpdateValueStrategy} configured by + * the current state of this editing object. + * + * @see UpdateValueStrategy#UpdateValueStrategy() + */ + public UpdateValueStrategy createM2TValueStrategy() { + return applyToM2TValueStrategy(new UpdateValueStrategy()); + } + + /** + * Creates a new model-to-target {@link UpdateValueStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new model-to-target {@link UpdateValueStrategy} configured by + * the current state of this editing object. + * + * @see UpdateValueStrategy#UpdateValueStrategy(int) + */ + public UpdateValueStrategy createM2TValueStrategy(int updatePolicy) { + return applyToM2TValueStrategy(new UpdateValueStrategy(updatePolicy)); + } + + /** + * Creates a new target-to-model {@link UpdateListStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new target-to-model {@link UpdateListStrategy} configured by + * the current state of this editing object. + * + * @see UpdateListStrategy#UpdateListStrategy() + */ + public UpdateListStrategy createT2MListStrategy() { + return applyToT2MListStrategy(new UpdateListStrategy()); + } + + /** + * Creates a new target-to-model {@link UpdateListStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new target-to-model {@link UpdateListStrategy} configured by + * the current state of this editing object. + * + * @see UpdateListStrategy#UpdateListStrategy(int) + */ + public UpdateListStrategy createT2MListStrategy(int updatePolicy) { + return applyToT2MListStrategy(new UpdateListStrategy(updatePolicy)); + } + + /** + * Creates a new model-to-target {@link UpdateListStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new model-to-target {@link UpdateListStrategy} configured by + * the current state of this editing object. + * + * @see UpdateListStrategy#UpdateListStrategy() + */ + public UpdateListStrategy createM2TListStrategy() { + return applyToM2TListStrategy(new UpdateListStrategy()); + } + + /** + * Creates a new model-to-target {@link UpdateListStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new model-to-target {@link UpdateListStrategy} configured by + * the current state of this editing object. + * + * @see UpdateListStrategy#UpdateListStrategy(int) + */ + public UpdateListStrategy createM2TListStrategy(int updatePolicy) { + return applyToM2TListStrategy(new UpdateListStrategy(updatePolicy)); + } + + /** + * Creates a new target-to-model {@link UpdateSetStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new target-to-model {@link UpdateSetStrategy} configured by the + * current state of this editing object. + * + * @see UpdateListStrategy#UpdateListStrategy() + */ + public UpdateSetStrategy createT2MSetStrategy() { + return applyToT2MSetStrategy(new UpdateSetStrategy()); + } + + /** + * Creates a new target-to-model {@link UpdateListStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new target-to-model {@link UpdateListStrategy} configured by + * the current state of this editing object. + * + * @see UpdateSetStrategy#UpdateSetStrategy(int) + */ + public UpdateSetStrategy createT2MSetStrategy(int updatePolicy) { + return applyToT2MSetStrategy(new UpdateSetStrategy(updatePolicy)); + } + + /** + * Creates a new model-to-target {@link UpdateSetStrategy} with a default + * update policy configured by the current state of this editing object. + * + * @return A new model-to-target {@link UpdateSetStrategy} configured by the + * current state of this editing object. + * + * @see UpdateSetStrategy#UpdateSetStrategy() + */ + public UpdateSetStrategy createM2TSetStrategy() { + return applyToM2TSetStrategy(new UpdateSetStrategy()); + } + + /** + * Creates a new model-to-target {@link UpdateSetStrategy} with the given + * update policy configured by the current state of this editing object. + * + * @param updatePolicy + * The update policy to use. + * @return A new model-to-target {@link UpdateSetStrategy} configured by the + * current state of this editing object. + * + * @see UpdateSetStrategy#UpdateSetStrategy(int) + */ + public UpdateSetStrategy createM2TSetStrategy(int updatePolicy) { + return applyToM2TSetStrategy(new UpdateSetStrategy(updatePolicy)); + } + + /** + * Configures an existing target-to-model {@link UpdateValueStrategy} with + * the current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateValueStrategy} to configure. + * @return The passed-in, configured target-to-model + * {@link UpdateValueStrategy}. + */ + public UpdateValueStrategy applyToT2MValueStrategy( + UpdateValueStrategy updateStrategy) { + updateStrategy + .setAfterGetValidator(targetConstraints.createValidator()); + updateStrategy.setConverter(targetConverter); + updateStrategy.setAfterConvertValidator(modelConstraints + .createValidator()); + updateStrategy.setBeforeSetValidator(beforeSetModelConstraints + .createValidator()); + return updateStrategy; + } + + /** + * Configures an existing model-to-target {@link UpdateValueStrategy} with + * the current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateValueStrategy} to configure. + * @return The passed-in, configured model-to-target + * {@link UpdateValueStrategy}. + */ + public UpdateValueStrategy applyToM2TValueStrategy( + UpdateValueStrategy updateStrategy) { + updateStrategy.setConverter(modelConverter); + return updateStrategy; + } + + /** + * Configures an existing target-to-model {@link UpdateListStrategy} with + * the current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateListStrategy} to configure. + * @return The passed-in, configured target-to-model + * {@link UpdateListStrategy}. + */ + public UpdateListStrategy applyToT2MListStrategy( + UpdateListStrategy updateStrategy) { + updateStrategy.setConverter(targetConverter); + return updateStrategy; + } + + /** + * Configures an existing model-to-target {@link UpdateListStrategy} with + * the current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateListStrategy} to configure. + * @return The passed-in, configured model-to-target + * {@link UpdateListStrategy}. + */ + public UpdateListStrategy applyToM2TListStrategy( + UpdateListStrategy updateStrategy) { + updateStrategy.setConverter(modelConverter); + return updateStrategy; + } + + /** + * Configures an existing target-to-model {@link UpdateListStrategy} with + * the current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateSetStrategy} to configure. + * @return The passed-in, configured target-to-model + * {@link UpdateSetStrategy}. + */ + public UpdateSetStrategy applyToT2MSetStrategy( + UpdateSetStrategy updateStrategy) { + updateStrategy.setConverter(targetConverter); + return updateStrategy; + } + + /** + * Configures an existing model-to-target {@link UpdateSetStrategy} with the + * current state of this editing object. + * + * @param updateStrategy + * The {@link UpdateSetStrategy} to configure. + * @return The passed-in, configured model-to-target + * {@link UpdateSetStrategy}. + */ + public UpdateSetStrategy applyToM2TSetStrategy( + UpdateSetStrategy updateStrategy) { + updateStrategy.setConverter(modelConverter); + return updateStrategy; + } + + /** + * Converts a target value to a model value by performing the following + * steps: + * + * + *

+ * The conversion process will be aborted by returning null + * whenever any of the applied validators produces a {@link IStatus + * validation status} having {@link IStatus#getSeverity() severity} + * IStatus.ERROR or IStatus.CANCEL. During the + * conversion process, any validation status whose severity is different + * from IStatus.OK will be {@link MultiStatus#add(IStatus) + * aggregated} on the given validationStatus. + *

+ * + * @param targetValue + * The target value to be converted to a model value. + * @param validationStatus + * Aggregate validation status to which to add the validations + * produced during the conversion process. + * @return The converted model value or null in case the + * conversion has been aborted due to a validation error. + */ + public final Object convertToModel(Object targetValue, + MultiStatus validationStatus) { + IValidator targetValidator = targetConstraints.createValidator(); + if (!applyValidator(targetValidator, targetValue, validationStatus)) { + return null; + } + + Object modelValue = (targetConverter != null) ? targetConverter + .convert(targetValue) : targetValue; + + IValidator modelValidator = modelConstraints.createValidator(); + if (!applyValidator(modelValidator, modelValue, validationStatus)) { + return null; + } + + IValidator beforeSetModelValidator = beforeSetModelConstraints + .createValidator(); + if (!applyValidator(beforeSetModelValidator, modelValue, + validationStatus)) { + return null; + } + + return modelValue; + } + + /** + * {@link #setModelConverter(IConverter) Converts} a model value to a target + * value. + * + * @param modelValue + * The target value to be converted to a model value. + * @return The converted target value. + */ + public final Object convertToTarget(Object modelValue) { + return (modelConverter != null) ? modelConverter.convert(modelValue) + : modelValue; + } + + private static boolean applyValidator(IValidator validator, Object value, + MultiStatus aggregateStatus) { + if (validator != null) { + IStatus validationStatus = validator.validate(value); + if (!validationStatus.isOK()) { + aggregateStatus.add(validationStatus); + } + return isValid(validationStatus); + } + return true; + } + + private static boolean isValid(IStatus status) { + return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING); + } +} Index: src/org/eclipse/core/databinding/editing/DateEditing.java =================================================================== RCS file: src/org/eclipse/core/databinding/editing/DateEditing.java diff -N src/org/eclipse/core/databinding/editing/DateEditing.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/editing/DateEditing.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.editing; + +import org.eclipse.core.databinding.validation.constraint.DateConstraints; +import org.eclipse.core.databinding.validation.constraint.StringConstraints; +import org.eclipse.core.internal.databinding.conversion.DateToStringConverter; +import org.eclipse.core.internal.databinding.conversion.StringToDateConverter; +import org.eclipse.core.internal.databinding.validation.StringToDateValidator; + +import com.ibm.icu.text.DateFormat; + +/** + * @since 1.3 + */ +public final class DateEditing extends Editing { + + private DateEditing(DateFormat[] inputFormats, String parseErrorMessage, + DateFormat displayFormat) { + super(new StringConstraints(), new DateConstraints(displayFormat), + new DateConstraints(displayFormat)); + + StringToDateConverter targetConverter; + if (inputFormats == null) { + targetConverter = new StringToDateConverter(); + } else { + targetConverter = new StringToDateConverter(inputFormats); + } + + targetConstraints().addValidator( + new StringToDateValidator(targetConverter, parseErrorMessage)); + setTargetConverter(targetConverter); + setModelConverter(displayFormat == null ? new DateToStringConverter() + : new DateToStringConverter(displayFormat)); + } + + /** + * Creates a new editing object which defaults the validations and + * conversions used for editing. Uses the default validation message. + * + * @return The new editing object using the default validations and + * conversions for editing. + */ + public static DateEditing withDefaults() { + return withDefaults(null); + } + + /** + * Creates a new editing object which defaults the validations and + * conversions used for editing. Uses the specified custom validation + * message. + * + * @param parseErrorMessage + * The validation message issued in case the input string cannot + * be parsed to a date. + * @return The new editing object using the default validations and + * conversions for editing. + */ + public static DateEditing withDefaults(String parseErrorMessage) { + return new DateEditing(null, parseErrorMessage, null); + } + + /** + * Creates a new editing object which supports all the given date input + * formats and which uses the specified format for displaying a date. Uses + * the default validation message. + * + * @param inputFormats + * The date formats supported for reading in dates. + * @param displayFormat + * The date format for displaying dates. + * @return The new editing object configured by the given date formats. + */ + public static DateEditing forFormats(DateFormat[] inputFormats, + DateFormat displayFormat) { + return new DateEditing(inputFormats, null, displayFormat); + } + + /** + * Creates a new editing object which supports all the given date input + * formats and which uses the specified format for displaying a date. Uses + * the specified custom validation message. + * + * @param inputFormats + * The date formats supported for reading in dates. + * @param parseErrorMessage + * The validation message issued in case the input string cannot + * be parsed to a date. + * @param displayFormat + * The date format for displaying dates. + * @return The new editing object configured by the given date formats. + */ + public static DateEditing forFormats(DateFormat[] inputFormats, + String parseErrorMessage, DateFormat displayFormat) { + return new DateEditing(inputFormats, parseErrorMessage, displayFormat); + } + + /** + * Returns the target constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link StringConstraints + * string target constraints} of this editing object and is equivalent to + * {@code (StringConstraints) targetConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #targetConstraints() + * @see StringConstraints + */ + public StringConstraints targetStringConstraints() { + return (StringConstraints) targetConstraints(); + } + + /** + * Returns the model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link DateConstraints date + * model constraints} of this editing object and is equivalent to {@code + * (DateConstraints) modelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #modelConstraints() + * @see DateConstraints + */ + public DateConstraints modelDateConstraints() { + return (DateConstraints) modelConstraints(); + } + + /** + * Returns the before-set model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link DateConstraints date + * before-set model constraints} of this editing object and is equivalent to + * {@code (DateConstraints) beforeSetModelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #beforeSetModelConstraints() + * @see DateConstraints + */ + public DateConstraints beforeSetModelDateConstraints() { + return (DateConstraints) beforeSetModelConstraints(); + } +} Index: src/org/eclipse/core/internal/databinding/conversion/StringStripConverter.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/conversion/StringStripConverter.java diff -N src/org/eclipse/core/internal/databinding/conversion/StringStripConverter.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/conversion/StringStripConverter.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.conversion; + +import org.eclipse.core.databinding.conversion.Converter; + +/** + * @since 1.3 + */ +public class StringStripConverter extends Converter { + + private final boolean stripToNull; + + /** + * + * @param stripToNull + */ + public StringStripConverter(boolean stripToNull) { + super(String.class, String.class); + this.stripToNull = stripToNull; + } + + public Object convert(Object fromObject) { + String string = (String) fromObject; + + if (string != null && string.length() != 0) { + int stripStart = 0; + while (stripStart < string.length() + && Character.isWhitespace(string.charAt(stripStart))) { + stripStart++; + } + + int stripEnd = string.length() - 1; + while (stripEnd >= 0 + && Character.isWhitespace(string.charAt(stripEnd))) { + stripEnd--; + } + + if (stripStart <= stripEnd) { + string = string.substring(stripStart, stripEnd + 1); + } else { + string = null; + } + } + + if (string == null || string.length() == 0) { + string = stripToNull ? null : ""; //$NON-NLS-1$ + } + + return string; + } +} Index: src/org/eclipse/core/internal/databinding/validation/DateRangeValidator.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/validation/DateRangeValidator.java diff -N src/org/eclipse/core/internal/databinding/validation/DateRangeValidator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/validation/DateRangeValidator.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.validation; + +import java.text.MessageFormat; +import java.util.Date; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.IStatus; + +import com.ibm.icu.text.DateFormat; + +/** + * @since 1.3 + */ +public class DateRangeValidator implements IValidator { + + private static final int UNDEFINED = -1; + private static final int AFTER = 0; + private static final int AFTER_EQUAL = 1; + private static final int BEFORE = 2; + private static final int BEFORE_EQUAL = 3; + + // TODO: Externalize + private static final String AFTER_MESSAGE = "The date must be after {0}."; //$NON-NLS-1$ + private static final String AFTER_EQUAL_MESSAGE = "The date must be after or on {0}."; //$NON-NLS-1$ + private static final String BEFORE_MESSAGE = "The date must be before {0}."; //$NON-NLS-1$ + private static final String BEFORE_EQUAL_MESSAGE = "The date must be before or on {0}."; //$NON-NLS-1$ + private static final String WITHIN_RANGE_MESSAGE = "The date must lie between {0} and {1}."; //$NON-NLS-1$ + + private final Date min; + private final Date max; + private final int minConstraint; + private final int maxConstraint; + private final String validationMessage; + private final DateFormat format; + + /** + * + * @param min + * @param max + * @param minConstraint + * @param maxConstraint + * @param validationMessage + * @param format + */ + private DateRangeValidator(Date min, Date max, int minConstraint, + int maxConstraint, String validationMessage, DateFormat format) { + this.min = min; + this.max = max; + this.minConstraint = minConstraint; + this.maxConstraint = maxConstraint; + this.validationMessage = validationMessage; + this.format = format; + } + + /** + * + * @param min + * @param validationMessage + * @param format + * @return . + */ + public static DateRangeValidator after(Date min, String validationMessage, + DateFormat format) { + return new DateRangeValidator(min, null, AFTER, UNDEFINED, + defaultIfNull(validationMessage, AFTER_MESSAGE), format); + } + + /** + * + * @param min + * @param validationMessage + * @param format + * @return . + */ + public static DateRangeValidator afterEqual(Date min, + String validationMessage, DateFormat format) { + return new DateRangeValidator(min, null, AFTER_EQUAL, UNDEFINED, + defaultIfNull(validationMessage, AFTER_EQUAL_MESSAGE), format); + } + + /** + * + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static DateRangeValidator before(Date max, String validationMessage, + DateFormat format) { + return new DateRangeValidator(null, max, UNDEFINED, BEFORE, + defaultIfNull(validationMessage, BEFORE_MESSAGE), format); + } + + /** + * + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static DateRangeValidator beforeEqual(Date max, + String validationMessage, DateFormat format) { + return new DateRangeValidator(null, max, UNDEFINED, BEFORE_EQUAL, + defaultIfNull(validationMessage, BEFORE_EQUAL_MESSAGE), format); + } + + /** + * + * @param min + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static DateRangeValidator range(Date min, Date max, + String validationMessage, DateFormat format) { + return new DateRangeValidator(min, max, AFTER_EQUAL, BEFORE_EQUAL, + defaultIfNull(validationMessage, WITHIN_RANGE_MESSAGE), format); + } + + public IStatus validate(Object value) { + if (value != null) { + Date date = (Date) value; + if ((min != null && !fulfillsConstraint(date, min, minConstraint)) + || (max != null && !fulfillsConstraint(date, max, + maxConstraint))) { + // TODO: Cache message? + return ValidationStatus.error(MessageFormat.format( + validationMessage, getFormattedRangeExtremas())); + } + } + return ValidationStatus.ok(); + } + + private boolean fulfillsConstraint(Date date1, Date date2, int constraint) { + switch (constraint) { + case AFTER: + return date1.after(date2); + case AFTER_EQUAL: + return date1.after(date2) || date1.equals(date2); + case BEFORE: + return date1.before(date2); + case BEFORE_EQUAL: + return date1.before(date2) || date1.equals(date2); + case UNDEFINED: + default: + throw new IllegalArgumentException( + "Unsupported constraint: " + constraint); //$NON-NLS-1$ + } + } + + private String[] getFormattedRangeExtremas() { + // TODO: Synchronize formatting to make it thread safe? + if (min == null) { + return new String[] { format.format(max) }; + } else if (max == null) { + return new String[] { format.format(min) }; + } + return new String[] { format.format(min), format.format(max) }; + } + + private static String defaultIfNull(String string, String defaultString) { + return (string != null) ? string : defaultString; + } +} Index: src/org/eclipse/core/databinding/validation/constraint/DateConstraints.java =================================================================== RCS file: src/org/eclipse/core/databinding/validation/constraint/DateConstraints.java diff -N src/org/eclipse/core/databinding/validation/constraint/DateConstraints.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/validation/constraint/DateConstraints.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.validation.constraint; + +import java.util.Date; + +import org.eclipse.core.internal.databinding.validation.DateRangeValidator; +import org.eclipse.core.internal.databinding.validation.NonNullValidator; + +import com.ibm.icu.text.DateFormat; + +/** + * @since 1.3 + */ +public final class DateConstraints extends Constraints { + + private final DateFormat displayFormat; + + private String requiredMessage = null; + + private String afterMessage = null; + + private String afterEqualMessage = null; + + private String beforeMessage = null; + + private String beforeEqualMessage = null; + + /** + * + * @param displayFormat + */ + public DateConstraints(DateFormat displayFormat) { + this.displayFormat = displayFormat; + } + + /** + * Adds a validator ensuring that the parsed date is not null. + * Uses the current {@link #requiredMessage(String) validation message}. + * + * @return This constraints instance for method chaining. + */ + public DateConstraints required() { + addValidator(new NonNullValidator(requiredMessage)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed date is + * null. + * + * @param message + * The validation message to be issued in case the parsed date is + * null. + * @return This constraints instance for method chaining. + * + * @see #required() + */ + public DateConstraints requiredMessage(String message) { + this.requiredMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed date is after the given date. + * Uses the current {@link #afterMessage(String) validation message}. + * + * @param date + * The date which must be before or on the parsed date. + * @return This constraints instance for method chaining. + * + * @see Date#after(Date) + */ + public DateConstraints after(Date date) { + addValidator(DateRangeValidator + .after(date, afterMessage, displayFormat)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed date is not + * after a given date. + * + * @param message + * The validation message to be issued in case the parsed date is + * not after a given date. + * @return This constraints instance for method chaining. + * + * @see #after(Date) + */ + public DateConstraints afterMessage(String message) { + this.afterMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed date is after or on the given + * date. Uses the current {@link #afterEqualMessage(String) validation + * message}. + * + * @param date + * The date which must be before the parsed date. + * @return This constraints instance for method chaining. + * + * @see Date#after(Date) + * @see Date#equals(Object) + */ + public DateConstraints afterEqual(Date date) { + addValidator(DateRangeValidator.afterEqual(date, afterEqualMessage, + displayFormat)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed date is not + * after or on a given date. + * + * @param message + * The validation message to be issued in case the parsed date is + * not after or on a given date. + * @return This constraints instance for method chaining. + * + * @see #afterEqual(Date) + */ + public DateConstraints afterEqualMessage(String message) { + this.afterEqualMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed date is before the given date. + * Uses the current {@link #beforeMessage(String) validation message}. + * + * @param date + * The date which must be after or on the parsed date. + * @return This constraints instance for method chaining. + * + * @see Date#before(Date) + */ + public DateConstraints before(Date date) { + addValidator(DateRangeValidator.before(date, beforeMessage, + displayFormat)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed date is not + * before a given date. + * + * @param message + * The validation message to be issued in case the parsed date is + * not before a given date. + * @return This constraints instance for method chaining. + * + * @see #before(Date) + */ + public DateConstraints beforeMessage(String message) { + this.beforeMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed date is before or on the given + * date. Uses the current {@link #beforeEqualMessage(String) validation + * message}. + * + * @param date + * The date which must be after the parsed date. + * @return This constraints instance for method chaining. + * + * @see Date#before(Date) + * @see Date#equals(Object) + */ + public DateConstraints beforeEqual(Date date) { + addValidator(DateRangeValidator.beforeEqual(date, beforeEqualMessage, + displayFormat)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed date is not + * before or on a given date. + * + * @param message + * The validation message to be issued in case the parsed date is + * not before or on a given date. + * @return This constraints instance for method chaining. + * + * @see #beforeEqual(Date) + */ + public DateConstraints beforeEqualMessage(String message) { + this.beforeEqualMessage = message; + return this; + } +} Index: src/org/eclipse/core/internal/databinding/validation/NonNullValidator.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/validation/NonNullValidator.java diff -N src/org/eclipse/core/internal/databinding/validation/NonNullValidator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/validation/NonNullValidator.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.validation; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.IStatus; + +/** + * @since 1.3 + */ +public class NonNullValidator implements IValidator { + + private final String validationMessage; + + /** + * + */ + public NonNullValidator() { + this(null); + } + + /** + * @param validationMessage + */ + public NonNullValidator(String validationMessage) { + // TODO: Externalize + this.validationMessage = validationMessage != null ? validationMessage + : "The value must not be empty."; //$NON-NLS-1$ + } + + public IStatus validate(Object value) { + if (value == null) { + return ValidationStatus.error(validationMessage); + } + return ValidationStatus.ok(); + } +} Index: src/org/eclipse/core/internal/databinding/validation/IntegerRangeValidator.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/validation/IntegerRangeValidator.java diff -N src/org/eclipse/core/internal/databinding/validation/IntegerRangeValidator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/validation/IntegerRangeValidator.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.validation; + +import com.ibm.icu.text.NumberFormat; + +/** + * @since 1.3 + */ +public class IntegerRangeValidator extends NumberRangeValidator { + + private static final Integer ZERO = new Integer(0); + private static final Integer ONE = new Integer(1); + + /** + * @param min + * @param max + * @param minConstraint + * @param maxConstraint + * @param validationMessage + * @param format + */ + private IntegerRangeValidator(Number min, Number max, int minConstraint, + int maxConstraint, String validationMessage, NumberFormat format) { + super(min, max, minConstraint, maxConstraint, validationMessage, format); + } + + /** + * + * @param min + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator greater(int min, + String validationMessage, NumberFormat format) { + return new IntegerRangeValidator(new Integer(min), null, GREATER, + UNDEFINED, defaultIfNull(validationMessage, GREATER_MESSAGE), + format); + } + + /** + * + * @param min + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator greaterEqual(int min, + String validationMessage, NumberFormat format) { + return new IntegerRangeValidator(new Integer(min), null, GREATER_EQUAL, + UNDEFINED, defaultIfNull(validationMessage, + GREATER_EQUAL_MESSAGE), format); + } + + /** + * + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator less(int max, String validationMessage, + NumberFormat format) { + return new IntegerRangeValidator(null, new Integer(max), UNDEFINED, + LESS, defaultIfNull(validationMessage, LESS_MESSAGE), format); + } + + /** + * + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator lessEqual(int max, + String validationMessage, NumberFormat format) { + return new IntegerRangeValidator(null, new Integer(max), UNDEFINED, + LESS_EQUAL, + defaultIfNull(validationMessage, LESS_EQUAL_MESSAGE), format); + } + + /** + * + * @param min + * @param max + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator range(int min, int max, + String validationMessage, NumberFormat format) { + return new IntegerRangeValidator(new Integer(min), new Integer(max), + GREATER_EQUAL, LESS_EQUAL, defaultIfNull(validationMessage, + WITHIN_RANGE_MESSAGE), format); + } + + /** + * + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator positive(String validationMessage, + NumberFormat format) { + return new IntegerRangeValidator(ONE, null, GREATER_EQUAL, UNDEFINED, + defaultIfNull(validationMessage, POSITIVE_MESSAGE), format); + } + + /** + * + * @param validationMessage + * @param format + * @return . + */ + public static IntegerRangeValidator nonNegative(String validationMessage, + NumberFormat format) { + return new IntegerRangeValidator(ZERO, null, GREATER_EQUAL, UNDEFINED, + defaultIfNull(validationMessage, NON_NEGATIVE_MESSAGE), format); + } + + protected int compare(Number number1, Number number2) { + Integer integer1 = (Integer) number1; + Integer integer2 = (Integer) number2; + return integer1.compareTo(integer2); + } + + private static String defaultIfNull(String string, String defaultString) { + return (string != null) ? string : defaultString; + } +} Index: src/org/eclipse/core/internal/databinding/validation/StringRegexValidator.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/validation/StringRegexValidator.java diff -N src/org/eclipse/core/internal/databinding/validation/StringRegexValidator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/validation/StringRegexValidator.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.validation; + +import java.text.MessageFormat; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.IStatus; + +/** + * @since 1.3 + */ +public class StringRegexValidator implements IValidator { + + // TODO: Externalize + private static final String REGEX_VALIDATION_MESSAGE = "The string must match the following regular expression: {0}."; //$NON-NLS-1$ + + private final Pattern pattern; + + private final String validationMessage; + + /** + * + * @param regex + */ + public StringRegexValidator(String regex) { + this(regex, null); + } + + /** + * + * @param regex + * @param validationMessage + */ + public StringRegexValidator(String regex, String validationMessage) { + this.pattern = Pattern.compile(regex); + this.validationMessage = validationMessage != null ? validationMessage + : REGEX_VALIDATION_MESSAGE; + } + + public IStatus validate(Object value) { + String input = (String) value; + if (input != null) { + Matcher matcher = pattern.matcher(input); + if (!matcher.matches()) { + // TODO: Cache message? + return ValidationStatus.error(MessageFormat.format( + validationMessage, new String[] { pattern.pattern() })); + } + } + return ValidationStatus.ok(); + } +} Index: src/org/eclipse/core/databinding/validation/constraint/StringConstraints.java =================================================================== RCS file: src/org/eclipse/core/databinding/validation/constraint/StringConstraints.java diff -N src/org/eclipse/core/databinding/validation/constraint/StringConstraints.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/validation/constraint/StringConstraints.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.validation.constraint; + +import java.util.regex.Pattern; + +import org.eclipse.core.internal.databinding.validation.NonNullValidator; +import org.eclipse.core.internal.databinding.validation.StringRegexValidator; + +/** + * @since 1.3 + */ +public final class StringConstraints extends Constraints { + + private String requiredMessage = null; + + private String matchesMessage = null; + + /** + * Adds a validator ensuring that the parsed integer is not + * null. Uses the current {@link #requiredMessage(String) + * validation message}. + * + * @return This constraints instance for method chaining. + */ + public StringConstraints required() { + addValidator(new NonNullValidator(requiredMessage)); + return this; + } + + /** + * Sets the validation message to be issued in case the input string is + * null. + * + * @param message + * The validation message to be issued in case the input string + * is null. + * @return This constraints instance for method chaining. + * + * @see #required() + */ + public StringConstraints requiredMessage(String message) { + this.requiredMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the input string + * {@link Pattern#matches(String, CharSequence) matches} the given regular + * expression . Uses the current {@link #matchesMessage(String) validation + * message}. + * + * @param regex + * The regular expression which the input string must match. + * @return This constraints instance for method chaining. + * + * @see Pattern#matches(String, CharSequence) + */ + public StringConstraints matches(String regex) { + addValidator(new StringRegexValidator(regex, matchesMessage)); + return this; + } + + /** + * Sets the validation message to be issued in case the input string does + * not match a given regular expression. + * + * @param message + * The validation message to be issued in case the input string + * does not match a given regular expression. + * @return This constraints instance for method chaining. + * + * @see #matches(String) + */ + public StringConstraints matchesMessage(String message) { + this.matchesMessage = message; + return this; + } +} Index: src/org/eclipse/core/databinding/editing/IntegerEditing.java =================================================================== RCS file: src/org/eclipse/core/databinding/editing/IntegerEditing.java diff -N src/org/eclipse/core/databinding/editing/IntegerEditing.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/editing/IntegerEditing.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.editing; + +import org.eclipse.core.databinding.conversion.NumberToStringConverter; +import org.eclipse.core.databinding.conversion.StringToNumberConverter; +import org.eclipse.core.databinding.validation.constraint.IntegerConstraints; +import org.eclipse.core.databinding.validation.constraint.StringConstraints; +import org.eclipse.core.internal.databinding.validation.StringToIntegerValidator; + +import com.ibm.icu.text.NumberFormat; + +/** + * @since 1.3 + */ +public final class IntegerEditing extends Editing { + + private IntegerEditing(NumberFormat format, String parseErrorMessage, + String outOfRangeMessage) { + super(new StringConstraints(), new IntegerConstraints(format), + new IntegerConstraints(format)); + + StringToNumberConverter targetConverter = StringToNumberConverter + .toInteger(format, false); + + targetConstraints().addValidator( + new StringToIntegerValidator(targetConverter, + parseErrorMessage, outOfRangeMessage)); + setTargetConverter(targetConverter); + setModelConverter(NumberToStringConverter.fromInteger(format, false)); + } + + /** + * Creates a new editing object which defaults the validations and + * conversions used for editing based on the platform's locale. Uses the + * default validation messages. + * + * @return The new editing object using the default validations and + * conversions for editing. + * + * @see NumberFormat#getIntegerInstance() + */ + public static IntegerEditing withDefaults() { + return withDefaults(null, null); + } + + /** + * Creates a new editing object which defaults the validations and + * conversions used for editing based on the platform's locale. Uses the + * specified custom validation messages. + * + * @param parseErrorMessage + * The validation message issued in case the input string cannot + * be parsed to an integer. + * @param outOfRangeMessage + * The validation message issued in case the input string + * represents an integer whose value is out of range. Can be + * parameterized by the Integer.MIN_VALUE and + * Integer.MAX_VALUE values. + * @return The new editing object using the default validations and + * conversions for editing. + * + * @see NumberFormat#getIntegerInstance() + */ + public static IntegerEditing withDefaults(String parseErrorMessage, + String outOfRangeMessage) { + return new IntegerEditing(NumberFormat.getIntegerInstance(), + parseErrorMessage, outOfRangeMessage); + } + + /** + * Creates a new editing object whose validations and conversions used for + * editing are based on the given integer format. Uses the default + * validation messages. + * + * @param format + * The integer format defining the validations and conversions + * used for editing. + * @return The new editing object configured by the given integer format. + */ + public static IntegerEditing forFormat(NumberFormat format) { + return forFormat(format, null, null); + } + + /** + * Creates a new editing object whose validations and conversions used for + * editing are based on the given integer format. Uses the specified custom + * validation messages. + * + * @param format + * The integer format defining the validations and conversions + * used for editing. + * @param parseErrorMessage + * The validation message issued in case the input string cannot + * be parsed to an integer. + * @param outOfRangeMessage + * The validation message issued in case the input string + * represents an integer whose value is out of range. Can be + * parameterized by the Integer.MIN_VALUE and + * Integer.MAX_VALUE values. + * @return The new editing object configured by the given integer format. + */ + public static IntegerEditing forFormat(NumberFormat format, + String parseErrorMessage, String outOfRangeMessage) { + return new IntegerEditing(format, parseErrorMessage, outOfRangeMessage); + } + + /** + * Returns the target constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link StringConstraints + * string target constraints} of this editing object and is equivalent to + * {@code (StringConstraints) targetConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #targetConstraints() + * @see StringConstraints + */ + public StringConstraints targetStringConstraints() { + return (StringConstraints) targetConstraints(); + } + + /** + * Returns the model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link IntegerConstraints + * integer model constraints} of this editing object and is equivalent to + * {@code (IntegerConstraints) modelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #modelConstraints() + * @see IntegerConstraints + */ + public IntegerConstraints modelIntegerConstraints() { + return (IntegerConstraints) modelConstraints(); + } + + /** + * Returns the before-set model constraints to apply. + * + *

+ * This method provides a typesafe access to the {@link IntegerConstraints + * integer before-set model constraints} of this editing object and is + * equivalent to {@code (IntegerConstraints) beforeSetModelConstraints()}. + *

+ * + * @return The target constraints to apply. + * + * @see #beforeSetModelConstraints() + * @see IntegerConstraints + */ + public IntegerConstraints beforeSetModelIntegerConstraints() { + return (IntegerConstraints) beforeSetModelConstraints(); + } +} Index: src/org/eclipse/core/databinding/validation/constraint/IntegerConstraints.java =================================================================== RCS file: src/org/eclipse/core/databinding/validation/constraint/IntegerConstraints.java diff -N src/org/eclipse/core/databinding/validation/constraint/IntegerConstraints.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/validation/constraint/IntegerConstraints.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,288 @@ +/******************************************************************************* + * Copyright (c) 2009 IBM Corporation 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: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.validation.constraint; + +import org.eclipse.core.internal.databinding.validation.IntegerRangeValidator; +import org.eclipse.core.internal.databinding.validation.NonNullValidator; + +import com.ibm.icu.text.NumberFormat; + +/** + * @since 1.3 + */ +public final class IntegerConstraints extends Constraints { + + private final NumberFormat format; + + private String requiredMessage = null; + + private String greaterMessage = null; + + private String greaterEqualMessage = null; + + private String lessMessage = null; + + private String lessEqualMessage = null; + + private String rangeMessage = null; + + private String positiveMessage = null; + + private String nonNegativeMessage = null; + + /** + * + * @param format + */ + public IntegerConstraints(NumberFormat format) { + this.format = format; + } + + /** + * Adds a validator ensuring that the parsed integer is not + * null. Uses the current {@link #requiredMessage(String) + * validation message}. + * + * @return This constraints instance for method chaining. + */ + public IntegerConstraints required() { + addValidator(new NonNullValidator(requiredMessage)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * null. + * + * @param message + * The validation message to be issued in case the parsed integer + * is null. + * @return This constraints instance for method chaining. + * + * @see #required() + */ + public IntegerConstraints requiredMessage(String message) { + this.requiredMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is greater than the + * given number. Uses the current {@link #greaterMessage(String) validation + * message}. + * + * @param number + * The number which the parsed integer must be greater than. + * @return This constraints instance for method chaining. + */ + public IntegerConstraints greater(int number) { + addValidator(IntegerRangeValidator.greater(number, greaterMessage, + format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not greater than a given number. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not greater than a given number. + * @return This constraints instance for method chaining. + * + * @see #greater(int) + */ + public IntegerConstraints greaterMessage(String message) { + this.greaterMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is greater than or + * equal to the given number. Uses the current + * {@link #greaterEqualMessage(String) validation message}. + * + * @param number + * The number which the parsed integer must be greater than or + * equal to. + * @return This constraints instance for method chaining. + */ + public IntegerConstraints greaterEqual(int number) { + addValidator(IntegerRangeValidator.greaterEqual(number, + greaterEqualMessage, format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not greater than or equal to a given number. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not greater than or equal to a given number. + * @return This constraints instance for method chaining. + * + * @see #greaterEqual(int) + */ + public IntegerConstraints greaterEqualMessage(String message) { + this.greaterEqualMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is less than the given + * number. Uses the current {@link #lessMessage(String) validation message}. + * + * @param number + * The number which the parsed integer must be less than. + * @return This constraints instance for method chaining. + */ + public IntegerConstraints less(int number) { + addValidator(IntegerRangeValidator.less(number, lessMessage, format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not less than a given number. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not less than a given number. + * @return This constraints instance for method chaining. + * + * @see #less(int) + */ + public IntegerConstraints lessMessage(String message) { + this.lessMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is less than or equal + * to the given number. Uses the current {@link #lessEqualMessage(String) + * validation message}. + * + * @param number + * The number which the parsed integer must be less than or equal + * to. + * @return This constraints instance for method chaining. + */ + public IntegerConstraints lessEqual(int number) { + addValidator(IntegerRangeValidator.greaterEqual(number, + lessEqualMessage, format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not less than or equal to a given number. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not less than or equal to a given number. + * @return This constraints instance for method chaining. + * + * @see #lessEqual(int) + */ + public IntegerConstraints lessEqualMessage(String message) { + this.lessEqualMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is within the given + * range. Uses the current {@link #rangeMessage(String) validation message}. + * + * @param min + * The lower bound of the range (inclusive). + * @param max + * The upper bound of the range (inclusive). + * @return This constraints instance for method chaining. + */ + public IntegerConstraints range(int min, int max) { + addValidator(IntegerRangeValidator + .range(min, max, rangeMessage, format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not within a given range. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not within a given range. + * @return This constraints instance for method chaining. + * + * @see #range(int, int) + */ + public IntegerConstraints rangeMessage(String message) { + this.rangeMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is positive. Uses the + * current {@link #positiveMessage(String) validation message}. + * + * @return This constraints instance for method chaining. + */ + public IntegerConstraints positive() { + addValidator(IntegerRangeValidator.positive(positiveMessage, format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * not positive. + * + * @param message + * The validation message to be issued in case the parsed integer + * is not positive. + * @return This constraints instance for method chaining. + * + * @see #positive() + */ + public IntegerConstraints positiveMessage(String message) { + this.positiveMessage = message; + return this; + } + + /** + * Adds a validator ensuring that the parsed integer is non-negative. Uses + * the current {@link #nonNegativeMessage(String) validation message}. + * + * @return This constraints instance for method chaining. + */ + public IntegerConstraints nonNegative() { + addValidator(IntegerRangeValidator.nonNegative(nonNegativeMessage, + format)); + return this; + } + + /** + * Sets the validation message to be issued in case the parsed integer is + * negative. + * + * @param message + * The validation message to be issued in case the parsed integer + * is negative. + * @return This constraints instance for method chaining. + * + * @see #nonNegative() + */ + public IntegerConstraints nonNegativeMessage(String message) { + this.nonNegativeMessage = message; + return this; + } +} Index: src/org/eclipse/core/internal/databinding/validation/NumberRangeValidator.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/validation/NumberRangeValidator.java diff -N src/org/eclipse/core/internal/databinding/validation/NumberRangeValidator.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/validation/NumberRangeValidator.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2009 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 183055) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.validation; + +import java.text.MessageFormat; + +import org.eclipse.core.databinding.validation.IValidator; +import org.eclipse.core.databinding.validation.ValidationStatus; +import org.eclipse.core.runtime.IStatus; + +import com.ibm.icu.text.NumberFormat; + +/** + * @since 1.3 + */ +public abstract class NumberRangeValidator implements IValidator { + + protected static final int UNDEFINED = -1; + protected static final int GREATER = 0; + protected static final int GREATER_EQUAL = 1; + protected static final int LESS = 2; + protected static final int LESS_EQUAL = 3; + + // TODO: Externalize + protected static final String GREATER_MESSAGE = "The value must be greater than {0}."; //$NON-NLS-1$ + protected static final String GREATER_EQUAL_MESSAGE = "The value must be greater than or equal to {0}."; //$NON-NLS-1$ + protected static final String LESS_MESSAGE = "The value must be less than {0}."; //$NON-NLS-1$ + protected static final String LESS_EQUAL_MESSAGE = "The value must be less than or equal to {0}."; //$NON-NLS-1$ + protected static final String WITHIN_RANGE_MESSAGE = "The value must lie between {0} and {1}."; //$NON-NLS-1$ + protected static final String POSITIVE_MESSAGE = "The value must be positive."; //$NON-NLS-1$ + protected static final String NON_NEGATIVE_MESSAGE = "The value must not be negative."; //$NON-NLS-1$ + + private final Number min; + private final Number max; + private final int minConstraint; + private final int maxConstraint; + private final String validationMessage; + private final NumberFormat format; + + /** + * + * @param min + * @param max + * @param minConstraint + * @param maxConstraint + * @param validationMessage + * @param format + */ + protected NumberRangeValidator(Number min, Number max, int minConstraint, + int maxConstraint, String validationMessage, NumberFormat format) { + this.min = min; + this.max = max; + this.minConstraint = minConstraint; + this.maxConstraint = maxConstraint; + this.validationMessage = validationMessage; + this.format = format; + } + + public IStatus validate(Object value) { + if (value != null) { + Number number = (Number) value; + if ((min != null && !fulfillsConstraint(number, min, minConstraint)) + || (max != null && !fulfillsConstraint(number, max, + maxConstraint))) { + // TODO: Cache message? + return ValidationStatus.error(MessageFormat.format( + validationMessage, getFormattedRangeExtremas())); + } + } + return ValidationStatus.ok(); + } + + private boolean fulfillsConstraint(Number number1, Number number2, + int constraint) { + int compareResult = compare(number1, number2); + switch (constraint) { + case GREATER: + return compareResult > 0; + case GREATER_EQUAL: + return compareResult >= 0; + case LESS: + return compareResult < 0; + case LESS_EQUAL: + return compareResult <= 0; + case UNDEFINED: + default: + throw new IllegalArgumentException( + "Unsupported constraint: " + constraint); //$NON-NLS-1$ + } + } + + protected abstract int compare(Number number1, Number number2); + + private String[] getFormattedRangeExtremas() { + // TODO: Synchronize formatting to make it thread safe? + if (min == null) { + return new String[] { format.format(max) }; + } else if (max == null) { + return new String[] { format.format(min) }; + } + return new String[] { format.format(min), format.format(max) }; + } +} #P org.eclipse.jface.tests.databinding Index: src/org/eclipse/core/tests/internal/databinding/BindingMessagesTest.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/BindingMessagesTest.java,v retrieving revision 1.1 diff -u -r1.1 BindingMessagesTest.java --- src/org/eclipse/core/tests/internal/databinding/BindingMessagesTest.java 1 Apr 2007 20:58:10 -0000 1.1 +++ src/org/eclipse/core/tests/internal/databinding/BindingMessagesTest.java 30 Aug 2009 12:08:37 -0000 @@ -21,13 +21,13 @@ public class BindingMessagesTest extends TestCase { public void testFormatString() throws Exception { String key = "Validate_NumberOutOfRangeError"; - String result = BindingMessages.formatString(key, new Object[] {"1", "2"}); + String result = BindingMessages.getFormattedString(key, new Object[] {"1", "2"}); assertFalse("key should not be returned", key.equals(result)); } public void testFormatStringForKeyNotFound() throws Exception { String key = "key_that_does_not_exist"; - String result = BindingMessages.formatString(key, null); + String result = BindingMessages.getFormattedString(key, null); assertTrue(key.equals(result)); } }