-package org.simantics.modeling.ui.componentTypeEditor;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-import org.eclipse.jface.dialogs.IMessageProvider;\r
-import org.eclipse.jface.layout.GridDataFactory;\r
-import org.eclipse.jface.layout.GridLayoutFactory;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.custom.StyledText;\r
-import org.eclipse.swt.custom.TableEditor;\r
-import org.eclipse.swt.events.SelectionAdapter;\r
-import org.eclipse.swt.events.SelectionEvent;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.widgets.Button;\r
-import org.eclipse.swt.widgets.Combo;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Label;\r
-import org.eclipse.swt.widgets.Shell;\r
-import org.eclipse.swt.widgets.Table;\r
-import org.eclipse.swt.widgets.TableItem;\r
-import org.eclipse.swt.widgets.Text;\r
-import org.eclipse.ui.forms.widgets.Form;\r
-import org.eclipse.ui.forms.widgets.FormToolkit;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.type.NumberType;\r
-import org.simantics.databoard.units.internal.library.UnitLibrary;\r
-import org.simantics.databoard.util.Limit;\r
-import org.simantics.databoard.util.Range;\r
-import org.simantics.databoard.util.RangeException;\r
-import org.simantics.db.RequestProcessor;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.NamedResource;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.userComponent.ComponentTypeCommands;\r
-import org.simantics.scl.runtime.function.Function4;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-\r
-public class ComponentTypeViewerData {\r
- /**\r
- * Used to validate property names.\r
- */\r
- public static final Pattern PROPERTY_NAME_PATTERN =\r
- Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");\r
-\r
- public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {\r
- "Double",\r
- "Integer",\r
- "Float",\r
- "String",\r
- "Boolean",\r
- "Long",\r
- "[Double]",\r
- "[Integer]",\r
- "[Float]",\r
- "[String]",\r
- "[Boolean]",\r
- "[Long]",\r
- "Vector Double",\r
- "Vector Integer",\r
- "Vector Float",\r
- "Vector String",\r
- "Vector Boolean",\r
- "Vector Long" \r
- };\r
- \r
- public Resource componentType;\r
- public FormToolkit tk;\r
- public Form form;\r
- public UnitLibrary unitLibrary = UnitLibrary.createDefault();\r
- public boolean readOnly;\r
- public NamedResource[] connectionPoints;\r
- public ComponentTypeViewerPropertyInfo[] properties;\r
-\r
- public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {\r
- this.tk = tk;\r
- this.componentType = componentType;\r
- this.form = form;\r
- }\r
- \r
- public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,\r
- Pattern namePattern) {\r
- int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
- final Text text = new Text(table, SWT.NONE | extraStyle);\r
- org.eclipse.swt.widgets.Listener listener = \r
- new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- if (e.type == SWT.Dispose) {\r
- form.setMessage(null);\r
- return;\r
- }\r
-\r
- if (e.type == SWT.Modify) {\r
- // validate current name\r
- String error = validatePropertyName(propertyInfo, text.getText(), namePattern);\r
- if (error != null) {\r
- text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));\r
- form.setMessage(error, IMessageProvider.ERROR);\r
- } else {\r
- text.setBackground(null);\r
- form.setMessage(null);\r
- }\r
- return;\r
- }\r
-\r
- if (e.type == SWT.Traverse) {\r
- if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
- text.dispose();\r
- e.doit = false;\r
- return;\r
- }\r
- if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)\r
- return;\r
- e.doit = false;\r
- }\r
- final String newValue = text.getText();\r
- text.dispose();\r
-\r
- String error = validatePropertyName(propertyInfo, newValue, namePattern);\r
- if (error != null)\r
- return;\r
-\r
- if (propertyInfo.immutable)\r
- return;\r
-\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph)\r
- throws DatabaseException {\r
- graph.markUndoPoint();\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);\r
- String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";\r
- String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);\r
- boolean setLabel = oldLabel == null\r
- || oldLabel.isEmpty()\r
- || oldCamelCasedLabel.isEmpty()\r
- || oldCamelCasedLabel.equals(oldLabel);\r
-\r
- ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);\r
- if (setLabel)\r
- ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));\r
- }\r
- });\r
- }\r
- };\r
- text.addListener(SWT.Modify, listener);\r
- text.addListener(SWT.Deactivate, listener);\r
- text.addListener(SWT.Traverse, listener);\r
- text.addListener(SWT.Dispose, listener);\r
-\r
- text.setText(selectedItem.getText(column));\r
- text.selectAll();\r
- text.setFocus();\r
-\r
- editor.setEditor(text, selectedItem, column);\r
- }\r
-\r
- public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {\r
- int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
- final Combo combo = new Combo(table, SWT.NONE | extraStyle);\r
- combo.setText(selectedItem.getText(column));\r
- for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)\r
- combo.add(suggestion);\r
- org.eclipse.swt.widgets.Listener listener = \r
- new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- if(e.type == SWT.Traverse) {\r
- if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
- combo.dispose();\r
- e.doit = false;\r
- return;\r
- }\r
- if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
- || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
- || e.detail == SWT.TRAVERSE_MNEMONIC)\r
- return;\r
- }\r
- final String newValue = combo.getText();\r
- if (e.type == SWT.Traverse) {\r
- e.doit = false;\r
- }\r
- combo.dispose();\r
-\r
- if (propertyInfo.immutable)\r
- return;\r
-\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph)\r
- throws DatabaseException {\r
- graph.markUndoPoint();\r
- ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);\r
- }\r
- });\r
- }\r
- };\r
- combo.setFocus();\r
- editor.setEditor(combo, selectedItem, column);\r
- combo.addListener(SWT.FocusOut, listener);\r
- combo.addListener(SWT.Traverse, listener);\r
- }\r
-\r
- protected void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {\r
- // Disallow unit editing for non-numeric configuration properties\r
- if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)\r
- return;\r
-\r
- int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
- final Combo combo = new Combo(table, SWT.NONE | extraStyle);\r
- String initialValue = selectedItem.getText(column);\r
- List<String> units = new ArrayList<>( unitLibrary.getUnits() );\r
- Collections.sort(units, String.CASE_INSENSITIVE_ORDER);\r
- int i = -1;\r
- int selected = -1;\r
- for (String unit : units) {\r
- combo.add(unit);\r
- if (unit.equals(initialValue))\r
- combo.select(i);\r
- }\r
- if (selected == -1)\r
- combo.setText(initialValue);\r
-\r
- org.eclipse.swt.widgets.Listener listener = \r
- new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- if(e.type == SWT.Traverse) {\r
- if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
- combo.dispose();\r
- e.doit = false;\r
- return;\r
- }\r
- if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
- || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
- || e.detail == SWT.TRAVERSE_MNEMONIC)\r
- return;\r
- }\r
- final String newValue = combo.getText();\r
- if(e.type == SWT.Traverse) {\r
- e.doit = false;\r
- }\r
- combo.dispose();\r
-\r
- if (propertyInfo.immutable)\r
- return;\r
-\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph)\r
- throws DatabaseException {\r
- graph.markUndoPoint();\r
- ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);\r
- }\r
- });\r
- }\r
- };\r
- combo.setFocus();\r
- editor.setEditor(combo, selectedItem, column);\r
- combo.addListener(SWT.Deactivate, listener);\r
- combo.addListener(SWT.Traverse, listener);\r
- }\r
-\r
- public void editValue(Table table, TableEditor editor,\r
- final ComponentTypeViewerPropertyInfo propertyInfo,\r
- TableItem selectedItem, int column,\r
- final StringWriter writer,\r
- final Function4<RequestProcessor, Resource, Resource, String, String> validator)\r
- {\r
- int extraStyle = writer == null ? SWT.READ_ONLY : 0;\r
- final Text text = new Text(table, SWT.NONE | extraStyle);\r
- text.setText(selectedItem.getText(column));\r
- org.eclipse.swt.widgets.Listener listener = \r
- new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- if(e.type == SWT.Traverse) {\r
- if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
- text.dispose();\r
- e.doit = false;\r
- return;\r
- }\r
- if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
- || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
- || e.detail == SWT.TRAVERSE_MNEMONIC)\r
- return;\r
- }\r
- final String newValue = text.getText();\r
- if(e.type == SWT.Traverse) {\r
- e.doit = false;\r
- }\r
- text.dispose();\r
-\r
- if (writer != null) {\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- writer.perform(graph, newValue);\r
- }\r
- });\r
- }\r
- }\r
- };\r
- text.selectAll();\r
- text.setFocus();\r
- editor.setEditor(text, selectedItem, column);\r
- text.addListener(SWT.FocusOut, listener);\r
- text.addListener(SWT.Traverse, listener);\r
-\r
- if (validator != null) {\r
- org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- final String newValue = text.getText();\r
- String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);\r
- if (error != null) {\r
- text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));\r
- text.setToolTipText(error);\r
- return;\r
- } else {\r
- text.setBackground(null);\r
- text.setToolTipText(null);\r
- }\r
- }\r
- };\r
- text.addListener(SWT.Modify, validationListener);\r
- }\r
- }\r
-\r
- private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {\r
- try {\r
- String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");\r
- return Range.valueOf(rangeStr);\r
- } catch (IllegalArgumentException e) {\r
- // Limits are invalid\r
- throw new RangeException(e.getMessage(), e);\r
- }\r
- }\r
- \r
- private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {\r
- Combo rng = new Combo(parent, SWT.READ_ONLY);\r
- rng.add("Inclusive");\r
- rng.add("Exclusive");\r
- rng.select(inclusive ? 0 : 1);\r
- return rng;\r
- }\r
- \r
- protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {\r
- // Disallow range editing when the property is not numeric\r
- if (propertyInfo.numberType == null)\r
- return;\r
-\r
- int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
-\r
- // Parse initial range value\r
- Range range = null;\r
- String rangeStr = selectedItem.getText(column);\r
- try {\r
- range = Range.valueOf(rangeStr);\r
- } catch (RangeException ex) {\r
- range = new Range(Limit.nolimit(), Limit.nolimit());\r
- }\r
-\r
- final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);\r
- GridLayoutFactory.fillDefaults().applyTo(shell);\r
-\r
- Composite composite = new Composite(shell, SWT.NONE);\r
- GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);\r
- GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);\r
-\r
- Label low = new Label(composite, SWT.NONE);\r
- low.setText("Minimum Value:");\r
- final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);\r
- GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);\r
- final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());\r
- Label high = new Label(composite, SWT.NONE);\r
- high.setText("Maximum Value:");\r
- final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);\r
- GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);\r
- final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());\r
-\r
- Composite buttonComposite = new Composite(shell, SWT.NONE);\r
- GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);\r
- GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);\r
-\r
- Button ok = new Button(buttonComposite, SWT.NONE);\r
- ok.setText("&OK");\r
- GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);\r
- Button cancel = new Button(buttonComposite, SWT.NONE);\r
- cancel.setText("&Cancel");\r
- GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);\r
-\r
- if (range.getLower().getValue() != null)\r
- lowText.setText(range.getLower().getValue().toString());\r
- if (range.getUpper().getValue() != null)\r
- highText.setText(range.getUpper().getValue().toString());\r
-\r
- shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event event) {\r
- shell.dispose();\r
- }\r
- });\r
-\r
- ok.addSelectionListener(new SelectionAdapter() {\r
- public void widgetSelected(SelectionEvent e) {\r
- try {\r
- final Range newRange = parseRange(propertyInfo.numberType,\r
- lowText.getText().trim(),\r
- highText.getText().trim(),\r
- lowSelector.getSelectionIndex() == 0 ? true : false,\r
- highSelector.getSelectionIndex() == 0 ? true : false);\r
-\r
- shell.dispose();\r
-\r
- if (propertyInfo.immutable)\r
- return;\r
-\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph)\r
- throws DatabaseException {\r
- graph.markUndoPoint();\r
- ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());\r
- }\r
- });\r
- } catch (RangeException ex) {\r
- ErrorLogger.defaultLogError(ex);\r
- }\r
- }\r
- });\r
- cancel.addSelectionListener(new SelectionAdapter() {\r
- public void widgetSelected(SelectionEvent e) {\r
- shell.dispose();\r
- }\r
- });\r
-\r
- shell.pack();\r
- Point size = shell.getSize();\r
-\r
- Display display = table.getDisplay();\r
- Rectangle clientArea = display.getClientArea();\r
- Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);\r
- Rectangle b = selectedItemBounds;\r
- b.x = bt.x;\r
- b.y = bt.y;\r
- b.width = size.x;\r
- b.height = size.y;\r
- if ((b.x + b.width) > clientArea.width)\r
- b.x -= b.x + b.width - clientArea.width;\r
- if (b.height > clientArea.height)\r
- b.height = clientArea.height;\r
- if ((b.y + b.height) > clientArea.height)\r
- b.y -= b.y + b.height - clientArea.height;\r
-\r
- shell.setBounds(selectedItemBounds);\r
- shell.open();\r
- }\r
-\r
- protected void editMultilineText(Table table, TableEditor editor,\r
- final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,\r
- Rectangle selectedItemBounds, int column, final StringWriter writer)\r
- {\r
- final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);\r
- GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);\r
- final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));\r
- GridDataFactory.fillDefaults().grab(true, true).applyTo(text);\r
- text.setText(selectedItem.getText(column));\r
- org.eclipse.swt.widgets.Listener listener = \r
- new org.eclipse.swt.widgets.Listener() {\r
- @Override\r
- public void handleEvent(Event e) {\r
- final String newValue = text.getText();\r
-\r
- if (e.type == SWT.Traverse) {\r
- if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
- shell.dispose();\r
- e.doit = false;\r
- return;\r
- }\r
- if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
- || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
- || e.detail == SWT.TRAVERSE_MNEMONIC)\r
- return;\r
- if ((e.stateMask & SWT.CTRL) == 0)\r
- return;\r
- e.doit = false;\r
- }\r
-\r
- shell.dispose();\r
-\r
- if (propertyInfo.immutable)\r
- return;\r
-\r
- if (writer != null) {\r
- Simantics.getSession().async(new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- writer.perform(graph, newValue);\r
- }\r
- });\r
- }\r
- }\r
- };\r
-\r
- String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";\r
- Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);\r
- GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);\r
- help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );\r
-\r
- Display display = table.getDisplay();\r
- Rectangle clientArea = display.getClientArea();\r
- Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);\r
- Rectangle b = selectedItemBounds;\r
- b.x = bt.x;\r
- b.y = bt.y;\r
- b.height = 200;\r
- if ((b.x + b.width) > clientArea.width)\r
- b.x -= b.x + b.width - clientArea.width;\r
- if (b.height > clientArea.height)\r
- b.height = clientArea.height;\r
- if ((b.y + b.height) > clientArea.height)\r
- b.y -= b.y + b.height - clientArea.height;\r
-\r
- shell.setBounds(selectedItemBounds);\r
- shell.open();\r
-\r
- text.selectAll();\r
- text.setFocus();\r
-\r
- text.addListener(SWT.Traverse, listener);\r
- shell.addListener(SWT.Deactivate, listener);\r
- }\r
-\r
- private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {\r
- if (propertyName.equals(propertyInfo.name))\r
- return null;\r
- for (ComponentTypeViewerPropertyInfo info : properties) {\r
- if (propertyName.equals(info.name))\r
- return "Property name '" + propertyName + "' is already in use.";\r
- }\r
- for (NamedResource cp : connectionPoints) {\r
- if (propertyName.equals(cp.getName()))\r
- return "Name '" + propertyName + "' is already used for a terminal.";\r
- }\r
- Matcher m = namePattern.matcher(propertyName);\r
- if (!m.matches())\r
- return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "\r
- + namePattern.pattern() + ".";\r
- return null;\r
- }\r
-\r
-}\r
+package org.simantics.modeling.ui.componentTypeEditor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.TableEditor;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.forms.widgets.Form;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.simantics.Simantics;
+import org.simantics.databoard.type.NumberType;
+import org.simantics.databoard.units.internal.library.UnitLibrary;
+import org.simantics.databoard.util.Limit;
+import org.simantics.databoard.util.Range;
+import org.simantics.databoard.util.RangeException;
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.NamedResource;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.function.DbConsumer;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.userComponent.ComponentTypeCommands;
+import org.simantics.scl.runtime.function.Function2;
+import org.simantics.scl.runtime.function.Function4;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.ErrorLogger;
+
+public class ComponentTypeViewerData {
+ /**
+ * Used to validate property names.
+ */
+ public static final Pattern PROPERTY_NAME_PATTERN =
+ Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
+
+ public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
+ "Double",
+ "Integer",
+ "Float",
+ "String",
+ "Boolean",
+ "Long",
+ "[Double]",
+ "[Integer]",
+ "[Float]",
+ "[String]",
+ "[Boolean]",
+ "[Long]",
+ "Vector Double",
+ "Vector Integer",
+ "Vector Float",
+ "Vector String",
+ "Vector Boolean",
+ "Vector Long"
+ };
+
+ public Resource componentType;
+ public FormToolkit tk;
+ public Form form;
+ public UnitLibrary unitLibrary = UnitLibrary.createDefault();
+ public boolean readOnly;
+ public NamedResource[] connectionPoints;
+ public ComponentTypeViewerPropertyInfo[] properties;
+
+ public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
+ this.tk = tk;
+ this.componentType = componentType;
+ this.form = form;
+ }
+
+ public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+ Pattern namePattern) {
+ editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
+ }
+
+ public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+ Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
+ editName(table, editor, propertyInfo, selectedItem, column,
+ null,
+ (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
+ extraWriter);
+ }
+
+ public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+ Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
+ editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
+ (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
+ extraWriter);
+ }
+
+ public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+ Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
+ {
+ editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
+ }
+
+ public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+ Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
+ editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
+ }
+
+ public void editName(
+ Table table,
+ TableEditor editor,
+ final ComponentTypeViewerPropertyInfo propertyInfo,
+ TableItem selectedItem,
+ int column,
+ Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
+ Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
+ DbConsumer<WriteGraph> extraWriter) {
+ int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+ final Text text = new Text(table, SWT.NONE | extraStyle);
+ org.eclipse.swt.widgets.Listener listener =
+ new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event e) {
+ if (e.type == SWT.Dispose) {
+ form.setMessage(null);
+ return;
+ } else if (e.type == SWT.Verify) {
+ // Filter input if necessary
+ e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
+ return;
+ } else if (e.type == SWT.Modify) {
+ // validate current name
+ String error = nameValidator.apply(propertyInfo, text.getText());
+ if (error != null) {
+ text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
+ form.setMessage(error, IMessageProvider.ERROR);
+ } else {
+ text.setBackground(null);
+ form.setMessage(null);
+ }
+ return;
+ } else if (e.type == SWT.Traverse) {
+ if (e.detail == SWT.TRAVERSE_ESCAPE) {
+ text.dispose();
+ e.doit = false;
+ return;
+ }
+ if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
+ return;
+ e.doit = false;
+ }
+ final String newValue = text.getText();
+ text.dispose();
+
+ String error = nameValidator.apply(propertyInfo, newValue);
+ if (error != null)
+ return;
+
+ if (propertyInfo.immutable)
+ return;
+
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph)
+ throws DatabaseException {
+ graph.markUndoPoint();
+ Layer0 L0 = Layer0.getInstance(graph);
+ String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
+ String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
+ String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
+ boolean setLabel = oldLabel == null
+ || oldLabel.isEmpty()
+ || oldCamelCasedLabel.isEmpty()
+ || oldCamelCasedLabel.equals(oldLabel);
+
+ ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
+ if (setLabel)
+ ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
+
+ if (extraWriter != null)
+ extraWriter.accept(graph);
+ }
+ });
+ }
+ };
+ if (nameFilter != null)
+ text.addListener(SWT.Verify, listener);
+ text.addListener(SWT.Modify, listener);
+ text.addListener(SWT.Deactivate, listener);
+ text.addListener(SWT.Traverse, listener);
+ text.addListener(SWT.Dispose, listener);
+
+ text.setText(selectedItem.getText(column));
+ text.selectAll();
+ text.setFocus();
+
+ editor.setEditor(text, selectedItem, column);
+ }
+
+ public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
+ int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+ final Combo combo = new Combo(table, SWT.NONE | extraStyle);
+ combo.setText(selectedItem.getText(column));
+ for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
+ combo.add(suggestion);
+ org.eclipse.swt.widgets.Listener listener =
+ new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event e) {
+ if(e.type == SWT.Traverse) {
+ if (e.detail == SWT.TRAVERSE_ESCAPE) {
+ combo.dispose();
+ e.doit = false;
+ return;
+ }
+ if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+ || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+ || e.detail == SWT.TRAVERSE_MNEMONIC)
+ return;
+ }
+ final String newValue = combo.getText();
+ if (e.type == SWT.Traverse) {
+ e.doit = false;
+ }
+ combo.dispose();
+
+ if (propertyInfo.immutable)
+ return;
+
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph)
+ throws DatabaseException {
+ graph.markUndoPoint();
+ ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
+ }
+ });
+ }
+ };
+ combo.setFocus();
+ editor.setEditor(combo, selectedItem, column);
+ combo.addListener(SWT.FocusOut, listener);
+ combo.addListener(SWT.Traverse, listener);
+ }
+
+ public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
+ // Disallow unit editing for non-numeric configuration properties
+ if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
+ return;
+
+ int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+ final Combo combo = new Combo(table, SWT.NONE | extraStyle);
+ String initialValue = selectedItem.getText(column);
+ List<String> units = new ArrayList<>( unitLibrary.getUnits() );
+ Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
+ int i = -1;
+ int selected = -1;
+ for (String unit : units) {
+ combo.add(unit);
+ if (unit.equals(initialValue))
+ combo.select(i);
+ }
+ if (selected == -1)
+ combo.setText(initialValue);
+
+ org.eclipse.swt.widgets.Listener listener =
+ new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event e) {
+ if(e.type == SWT.Traverse) {
+ if (e.detail == SWT.TRAVERSE_ESCAPE) {
+ combo.dispose();
+ e.doit = false;
+ return;
+ }
+ if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+ || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+ || e.detail == SWT.TRAVERSE_MNEMONIC)
+ return;
+ }
+ final String newValue = combo.getText();
+ if(e.type == SWT.Traverse) {
+ e.doit = false;
+ }
+ combo.dispose();
+
+ if (propertyInfo.immutable)
+ return;
+
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph)
+ throws DatabaseException {
+ graph.markUndoPoint();
+ ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
+ }
+ });
+ }
+ };
+ combo.setFocus();
+ editor.setEditor(combo, selectedItem, column);
+ combo.addListener(SWT.Deactivate, listener);
+ combo.addListener(SWT.Traverse, listener);
+ }
+
+ public void editValue(Table table, TableEditor editor,
+ final ComponentTypeViewerPropertyInfo propertyInfo,
+ TableItem selectedItem, int column,
+ final StringWriter writer,
+ final Function4<RequestProcessor, Resource, Resource, String, String> validator)
+ {
+ int extraStyle = writer == null ? SWT.READ_ONLY : 0;
+ final Text text = new Text(table, SWT.NONE | extraStyle);
+ text.setText(selectedItem.getText(column));
+ org.eclipse.swt.widgets.Listener listener =
+ new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event e) {
+ if(e.type == SWT.Traverse) {
+ if (e.detail == SWT.TRAVERSE_ESCAPE) {
+ text.dispose();
+ e.doit = false;
+ return;
+ }
+ if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+ || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+ || e.detail == SWT.TRAVERSE_MNEMONIC)
+ return;
+ }
+ final String newValue = text.getText();
+ if(e.type == SWT.Traverse) {
+ e.doit = false;
+ }
+ text.dispose();
+
+ if (validator != null) {
+ String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
+ if (error != null)
+ return;
+ }
+
+ if (writer != null) {
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ writer.perform(graph, newValue);
+ }
+ });
+ }
+ }
+ };
+ text.selectAll();
+ text.setFocus();
+ editor.setEditor(text, selectedItem, column);
+ text.addListener(SWT.FocusOut, listener);
+ text.addListener(SWT.Traverse, listener);
+
+ if (validator != null) {
+ org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
+
+ private ScheduledFuture<?> future;
+
+ @Override
+ public void handleEvent(Event e) {
+ final String newValue = text.getText();
+ if (future != null && !future.isCancelled())
+ future.cancel(true);
+ future = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
+ String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
+ if (!text.isDisposed()) {
+ text.getDisplay().asyncExec(() -> {
+ if (!text.isDisposed()) {
+ if (error != null) {
+ text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
+ text.setToolTipText(error);
+ return;
+ } else {
+ text.setBackground(null);
+ text.setToolTipText(null);
+ }
+ }
+
+ });
+ }
+ }, 500, TimeUnit.MILLISECONDS);
+ }
+ };
+ text.addListener(SWT.Modify, validationListener);
+ }
+ }
+
+ private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
+ try {
+ String rangeStr = (lowInclusive ? "[" : "(") + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
+ return Range.valueOf(rangeStr);
+ } catch (IllegalArgumentException e) {
+ // Limits are invalid
+ throw new RangeException(e.getMessage(), e);
+ }
+ }
+
+ private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
+ Combo rng = new Combo(parent, SWT.READ_ONLY);
+ rng.add("Inclusive");
+ rng.add("Exclusive");
+ rng.select(inclusive ? 0 : 1);
+ return rng;
+ }
+
+ protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
+ // Disallow range editing when the property is not numeric
+ if (propertyInfo.numberType == null)
+ return;
+
+ int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+
+ // Parse initial range value
+ Range range = null;
+ String rangeStr = selectedItem.getText(column);
+ try {
+ range = Range.valueOf(rangeStr);
+ } catch (RangeException ex) {
+ range = new Range(Limit.nolimit(), Limit.nolimit());
+ }
+
+ final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
+ GridLayoutFactory.fillDefaults().applyTo(shell);
+
+ Composite composite = new Composite(shell, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
+ GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
+
+ Label low = new Label(composite, SWT.NONE);
+ low.setText("Minimum Value:");
+ final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
+ GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
+ final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
+ Label high = new Label(composite, SWT.NONE);
+ high.setText("Maximum Value:");
+ final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
+ GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
+ final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
+
+ Composite buttonComposite = new Composite(shell, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
+ GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
+
+ Button ok = new Button(buttonComposite, SWT.NONE);
+ ok.setText("&OK");
+ GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
+ Button cancel = new Button(buttonComposite, SWT.NONE);
+ cancel.setText("&Cancel");
+ GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
+
+ if (range.getLower().getValue() != null)
+ lowText.setText(range.getLower().getValue().toString());
+ if (range.getUpper().getValue() != null)
+ highText.setText(range.getUpper().getValue().toString());
+
+ shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ shell.dispose();
+ }
+ });
+
+ ok.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ try {
+ final Range newRange = parseRange(propertyInfo.numberType,
+ lowText.getText().trim(),
+ highText.getText().trim(),
+ lowSelector.getSelectionIndex() == 0 ? true : false,
+ highSelector.getSelectionIndex() == 0 ? true : false);
+
+ shell.dispose();
+
+ if (propertyInfo.immutable)
+ return;
+
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph)
+ throws DatabaseException {
+ graph.markUndoPoint();
+ ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
+ }
+ });
+ } catch (RangeException ex) {
+ ErrorLogger.defaultLogError(ex);
+ }
+ }
+ });
+ cancel.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ shell.dispose();
+ }
+ });
+
+ shell.pack();
+ Point size = shell.getSize();
+
+ Display display = table.getDisplay();
+ Rectangle clientArea = display.getClientArea();
+ Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
+ Rectangle b = selectedItemBounds;
+ b.x = bt.x;
+ b.y = bt.y;
+ b.width = size.x;
+ b.height = size.y;
+ if ((b.x + b.width) > clientArea.width)
+ b.x -= b.x + b.width - clientArea.width;
+ if (b.height > clientArea.height)
+ b.height = clientArea.height;
+ if ((b.y + b.height) > clientArea.height)
+ b.y -= b.y + b.height - clientArea.height;
+
+ shell.setBounds(selectedItemBounds);
+ shell.open();
+ }
+
+ public void editMultilineText(Table table, TableEditor editor,
+ final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
+ Rectangle selectedItemBounds, int column, final StringWriter writer)
+ {
+ final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
+ GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
+ final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
+ GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
+ text.setText(selectedItem.getText(column));
+ org.eclipse.swt.widgets.Listener listener =
+ new org.eclipse.swt.widgets.Listener() {
+ @Override
+ public void handleEvent(Event e) {
+ final String newValue = text.getText();
+
+ if (e.type == SWT.Traverse) {
+ if (e.detail == SWT.TRAVERSE_ESCAPE) {
+ shell.dispose();
+ e.doit = false;
+ return;
+ }
+ if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+ || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+ || e.detail == SWT.TRAVERSE_MNEMONIC)
+ return;
+ if ((e.stateMask & SWT.CTRL) == 0)
+ return;
+ e.doit = false;
+ }
+
+ shell.dispose();
+
+ if (propertyInfo.immutable)
+ return;
+
+ if (writer != null) {
+ Simantics.getSession().async(new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ writer.perform(graph, newValue);
+ }
+ });
+ }
+ }
+ };
+
+ String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
+ Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
+ GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
+ help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
+
+ Display display = table.getDisplay();
+ Rectangle clientArea = display.getClientArea();
+ Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
+ Rectangle b = selectedItemBounds;
+ b.x = bt.x;
+ b.y = bt.y;
+ b.height = 200;
+ if ((b.x + b.width) > clientArea.width)
+ b.x -= b.x + b.width - clientArea.width;
+ if (b.height > clientArea.height)
+ b.height = clientArea.height;
+ if ((b.y + b.height) > clientArea.height)
+ b.y -= b.y + b.height - clientArea.height;
+
+ shell.setBounds(selectedItemBounds);
+ shell.open();
+
+ text.selectAll();
+ text.setFocus();
+
+ text.addListener(SWT.Traverse, listener);
+ shell.addListener(SWT.Deactivate, listener);
+ }
+
+ private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
+ if (propertyName.equals(propertyInfo.name))
+ return null;
+ for (ComponentTypeViewerPropertyInfo info : properties) {
+ if (propertyName.equals(info.name))
+ return "Property name '" + propertyName + "' is already in use.";
+ }
+ for (NamedResource cp : connectionPoints) {
+ if (propertyName.equals(cp.getName()))
+ return "Name '" + propertyName + "' is already used for a terminal.";
+ }
+ Matcher m = namePattern.matcher(propertyName);
+ if (!m.matches())
+ return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
+ + namePattern.pattern() + ".";
+ return null;
+ }
+
+}