/* * Copyright (c) 2013 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.ui.functions.groovy.internal; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.xml.namespace.QName; import org.eclipse.jface.dialogs.DialogTray; import org.eclipse.jface.dialogs.TrayDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.text.source.SourceViewerConfiguration; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ITreeSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.dialogs.FilteredTree; import org.eclipse.ui.dialogs.PatternFilter; import com.google.common.collect.ImmutableList; import eu.esdihumboldt.cst.functions.groovy.GroovyConstants; import eu.esdihumboldt.hale.common.schema.SchemaSpaceID; import eu.esdihumboldt.hale.common.schema.groovy.DefinitionAccessor; import eu.esdihumboldt.hale.common.schema.model.ChildDefinition; import eu.esdihumboldt.hale.common.schema.model.Definition; import eu.esdihumboldt.hale.common.schema.model.DefinitionGroup; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.constraint.property.Cardinality; import eu.esdihumboldt.hale.common.schema.model.constraint.type.AugmentedValueFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.HasValueFlag; import eu.esdihumboldt.hale.ui.HaleWizardPage; import eu.esdihumboldt.hale.ui.common.CommonSharedImages; import eu.esdihumboldt.hale.ui.common.definition.viewer.DefinitionComparator; import eu.esdihumboldt.hale.ui.common.definition.viewer.SchemaPatternFilter; import eu.esdihumboldt.hale.ui.common.definition.viewer.StyledDefinitionLabelProvider; import eu.esdihumboldt.hale.ui.common.definition.viewer.TypeDefinitionContentProvider; import eu.esdihumboldt.hale.ui.common.definition.viewer.TypePropertyContentProvider; import eu.esdihumboldt.hale.ui.util.IColorManager; import eu.esdihumboldt.hale.ui.util.groovy.GroovyColorManager; import eu.esdihumboldt.hale.ui.util.groovy.GroovySourceViewerUtil; import eu.esdihumboldt.hale.ui.util.groovy.SimpleGroovySourceViewerConfiguration; import eu.esdihumboldt.hale.ui.util.viewer.tree.TreePathFilteredTree; import eu.esdihumboldt.hale.ui.util.viewer.tree.TreePathProviderAdapter; /** * Dialog tray displaying a type structure. * * @author Simon Templer */ public class TypeStructureTray extends DialogTray implements GroovyConstants { /** * Name for a dummy type where the properties actually represent variables. */ public static final QName VARIABLES_TYPE_NAME = new QName("http://esdi-humboldt.eu/dummy", "VariablesType"); /** * If in the target example builder code brackets should be used. */ private static final boolean BUILDER_USE_BRACKETS = true; /** * Retrieves a list of types. */ public interface TypeProvider { /** * @return the collection of associated types */ public Collection<? extends TypeDefinition> getTypes(); } /** * Create a tool item for displaying the source or target type structure in * the dialog tray. * * @param bar the tool bar to add the item to * @param page the associated wizard page * @param types the provider for the types to display * @param schemaSpace the schema space */ public static void createToolItem(ToolBar bar, final HaleWizardPage<?> page, final SchemaSpaceID schemaSpace, final TypeProvider types) { ToolItem item = new ToolItem(bar, SWT.PUSH); switch (schemaSpace) { case SOURCE: item.setImage(CommonSharedImages.getImageRegistry().get( CommonSharedImages.IMG_SOURCE_SCHEMA)); item.setToolTipText("Show source structure"); break; case TARGET: item.setImage(CommonSharedImages.getImageRegistry().get( CommonSharedImages.IMG_TARGET_SCHEMA)); item.setToolTipText("Show target structure"); break; } item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (page.getContainer() instanceof TrayDialog) { TrayDialog dialog = (TrayDialog) page.getContainer(); // close existing tray if (dialog.getTray() != null) { dialog.closeTray(); } dialog.openTray(new TypeStructureTray(types, schemaSpace)); } else { // TODO show dialog instead? } } }); } private final TypeProvider types; private final SchemaSpaceID schemaSpace; /** * Create a type structure tray. * * @param types the type provider * @param schemaSpace the schema space */ public TypeStructureTray(TypeProvider types, SchemaSpaceID schemaSpace) { super(); this.types = types; this.schemaSpace = schemaSpace; } @Override protected Control createContents(Composite parent) { Composite page = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(page); // retrieve the types final Collection<? extends TypeDefinition> types = this.types.getTypes(); // heading Label caption = new Label(page, SWT.NONE); switch (schemaSpace) { case SOURCE: caption.setText("Source structure"); break; case TARGET: caption.setText("Target structure"); break; } caption.setFont(JFaceResources.getHeaderFont()); // create tree viewer PatternFilter patternFilter = new SchemaPatternFilter(); patternFilter.setIncludeLeadingWildcard(true); final FilteredTree filteredTree = new TreePathFilteredTree(page, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER, patternFilter, true); TreeViewer tree = filteredTree.getViewer(); tree.setUseHashlookup(true); StyledDefinitionLabelProvider labelProvider = new StyledDefinitionLabelProvider(tree); tree.setLabelProvider(labelProvider); IContentProvider contentProvider = createContentProvider(tree); tree.setContentProvider(contentProvider); GridDataFactory.fillDefaults().grab(true, true).hint(280, 400).applyTo(filteredTree); tree.setComparator(new DefinitionComparator()); // set input if (types.size() == 1) { tree.setInput(types.iterator().next()); } else { tree.setInput(types); } /* * Groovy specific part */ // caption Label example = new Label(page, SWT.NONE); switch (schemaSpace) { case SOURCE: example.setText("Examples: Access variables"); break; case TARGET: example.setText("Example: Build instance"); break; default: example.setText("Example"); } // source viewer final SourceViewer viewer = new SourceViewer(page, null, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY); final IColorManager colorManager = new GroovyColorManager(); SourceViewerConfiguration configuration = new SimpleGroovySourceViewerConfiguration( colorManager, ImmutableList.of(BINDING_TARGET, BINDING_BUILDER, BINDING_INDEX, BINDING_SOURCE, BINDING_SOURCE_TYPES, BINDING_TARGET_TYPE, BINDING_CELL, BINDING_LOG, BINDING_CELL_CONTEXT, BINDING_FUNCTION_CONTEXT, BINDING_TRANSFORMATION_CONTEXT), null); viewer.configure(configuration); GridDataFactory.fillDefaults().grab(true, false).hint(200, 130) .applyTo(viewer.getControl()); // make sure the color manager is disposed viewer.getControl().addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { colorManager.dispose(); } }); // react to tree selection changes tree.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IDocument doc = new Document(); GroovySourceViewerUtil.setupDocument(doc); String example = null; if (!event.getSelection().isEmpty()) { switch (schemaSpace) { case SOURCE: example = createSourceSample(event.getSelection(), types); break; case TARGET: example = createTargetSample(event.getSelection(), types); break; } } if (example == null || example.isEmpty()) { switch (schemaSpace) { case SOURCE: doc.set("// Please select schema elements to access"); break; case TARGET: doc.set("// Please select which schema elements\n// to include in the instance to build"); break; default: doc.set("// Please select one or more schema elements"); } } else { doc.set(example); } viewer.setDocument(doc); } }); tree.setSelection(StructuredSelection.EMPTY); return page; } /** * * Returns an appropriate content provider * * @param tree a tree viewer * * @return content provider */ protected IContentProvider createContentProvider(TreeViewer tree) { IContentProvider contentProvider; if (types.getTypes().size() == 1) { contentProvider = new TreePathProviderAdapter(new TypePropertyContentProvider(tree)); } else { contentProvider = new TreePathProviderAdapter(new TypeDefinitionContentProvider(tree)); } return contentProvider; } /** * Create sample code for accessing a source property. * * @param selection the selection in the tree viewer * @param types the types serving as input * @return the sample code or <code>null</code> */ protected String createSourceSample(ISelection selection, Collection<? extends TypeDefinition> types) { ITreeSelection treeSel = (ITreeSelection) selection; TreePath[] paths = treeSel.getPaths(); if (paths != null && paths.length > 0) { StringBuilder result = null; for (TreePath path : paths) { String examples = createSourceSample(path, types); if (examples != null) { if (result == null) { result = new StringBuilder(); } result.append(examples); } } if (result != null) { return result.toString(); } return null; } return null; } /** * Create sample code for a single tree path specifying a source property. * * @param path the tree path * @param types the types serving as input * @return the sample code or <code>null</code> */ private String createSourceSample(TreePath path, Collection<? extends TypeDefinition> types) { DefinitionGroup parent; int startIndex = 0; boolean hasPropDef = false; StringBuilder access = new StringBuilder(); // determine parent type if (path.getFirstSegment() instanceof TypeDefinition) { // types are the top level elements parent = (DefinitionGroup) path.getFirstSegment(); startIndex = 1; access.append(GroovyConstants.BINDING_SOURCE); } else { // types are not in the tree, single type must be root TypeDefinition type = types.iterator().next(); parent = type; if (VARIABLES_TYPE_NAME.equals(type.getName())) { // Groovy property transformation // first segment is variable name Definition<?> def = (Definition<?>) path.getFirstSegment(); startIndex++; access.append(def.getName().getLocalPart()); // XXX add namespace if necessary // XXX currently groovy transformation does not support multiple // variables with the same name parent = DefinitionUtil.getDefinitionGroup(def); } else { // assuming Retype/Merge access.append(GroovyConstants.BINDING_SOURCE); } } // is a property or list of inputs referenced -> use accessor boolean propertyOrList = path.getSegmentCount() > startIndex || (path.getLastSegment() instanceof ChildDefinition<?> && canOccureMultipleTimes((ChildDefinition<?>) path .getLastSegment())); if (!propertyOrList) { if (parent instanceof TypeDefinition && canHaveValue((TypeDefinition) parent)) { if (!DefinitionUtil.hasChildren((Definition<?>) path.getLastSegment())) { // variable w/o children referenced return "// access variable\ndef value = " + access; } else { // variable w/ children referenced return "// access instance variable value\ndef value = " + access + ".value"; } } else { // return null; } } if (path.getFirstSegment() instanceof TypeDefinition) { access.append(".links." + ((TypeDefinition) path.getFirstSegment()).getDisplayName()); } else { access.append(".p"); // check for encountering property definition, so that there should // not be 2 'p's appended. hasPropDef = true; } for (int i = startIndex; i < path.getSegmentCount(); i++) { Definition<?> def = (Definition<?>) path.getSegment(i); if (def instanceof PropertyDefinition) { // check if the previous segments are type definition for (int j = i - 1; j >= 0; j--) { Definition<?> def1 = (Definition<?>) path.getSegment(j); if (def1 instanceof TypeDefinition) { // do nothing } else { hasPropDef = true; } } if (!hasPropDef) { access.append(".p"); } // property name access.append('.'); access.append(def.getName().getLocalPart()); // test if uniquely accessible from parent boolean useNamespace = true; if (parent instanceof Definition<?>) { useNamespace = namespaceNeeded((Definition<?>) parent, def); } // add namespace if necessary if (useNamespace) { access.append("('"); access.append(def.getName().getNamespaceURI()); access.append("')"); } } else if (def instanceof TypeDefinition) { access.append("." + ((TypeDefinition) def).getDisplayName()); } // set the new parent parent = DefinitionUtil.getDefinitionGroup(def); } if (parent instanceof TypeDefinition) { // only properties at the end of the path are supported TypeDefinition propertyType = (TypeDefinition) parent; StringBuilder example = new StringBuilder(); boolean canOccurMultipleTimes = false; if (path.getFirstSegment() instanceof TypeDefinition) { canOccurMultipleTimes = true; } /* * Instances/values may occur multiple times if any element in the * path may occur multiple times. */ for (int i = path.getSegmentCount() - 1; i >= 0 && !canOccurMultipleTimes; i--) { if (path.getSegment(i) instanceof ChildDefinition<?>) { canOccurMultipleTimes = canOccureMultipleTimes((ChildDefinition<?>) path .getSegment(i)); } } // check different cases if (canHaveValue(propertyType)) { // referenced property is a value or has a value // single value if (canOccurMultipleTimes) { example.append("// access first value\n"); } else { example.append("// access value\n"); } example.append("def value = "); example.append(access); example.append(".value()\n\n"); // multiple values if (canOccurMultipleTimes) { example.append("// access all values as list\n"); example.append("def valueList = "); example.append(access); example.append(".values()\n\n"); } } if (DefinitionUtil.hasChildren(propertyType)) { // referenced property is an instance // single instance if (canOccurMultipleTimes) { example.append("// access first instance\n"); } else { example.append("// access instance\n"); } example.append("def instance = "); example.append(access); example.append(".first()\n\n"); if (canOccurMultipleTimes) { // multiple values example.append("// access all instances as list\n"); example.append("def instanceList = "); example.append(access); example.append(".list()\n\n"); // iterate over instances example.append("// iterate over instances\n"); example.append(access); example.append(".each {\n"); example.append("\tinstance ->\n"); example.append("}\n\n"); } } else if (canOccurMultipleTimes && propertyType.getConstraint(HasValueFlag.class).isEnabled()) { // iterate over values example.append("// iterate over values\n"); example.append(access); example.append(".each {\n"); example.append("\tvalue ->\n"); example.append("}\n\n"); } return example.toString(); } return null; } private boolean canOccureMultipleTimes(ChildDefinition<?> child) { Cardinality card = DefinitionUtil.getCardinality(child); return card.getMaxOccurs() == Cardinality.UNBOUNDED || card.getMaxOccurs() > 1; } private boolean canHaveValue(TypeDefinition propertyType) { return propertyType.getConstraint(HasValueFlag.class).isEnabled() || propertyType.getConstraint(AugmentedValueFlag.class).isEnabled(); } private boolean namespaceNeeded(Definition<?> parent, Definition<?> child) { boolean namespaceNeeded = true; try { new DefinitionAccessor(parent).findChildren(child.getName().getLocalPart()).eval(); namespaceNeeded = false; } catch (IllegalStateException e) { // ignore - namespace needed } return namespaceNeeded; } /** * Create sample code for populating the target property. * * @param selection the selection in the tree viewer * @param types the types serving as input * @return the sample code */ protected String createTargetSample(ISelection selection, Collection<? extends TypeDefinition> types) { ITreeSelection treeSel = (ITreeSelection) selection; TreePath[] paths = treeSel.getPaths(); if (paths != null && paths.length > 0) { // XXX for now only use the first path TreePath path = paths[0]; DefinitionGroup parent; int startIndex = 0; List<PathTree> properties; // determine parent type if (path.getFirstSegment() instanceof TypeDefinition) { // types are the top level elements // parent = (DefinitionGroup) path.getFirstSegment(); // startIndex = 1; // XXX not supported yet return null; } else { // types are not in the tree, single type must be root parent = types.iterator().next(); // build PathTrees from tree paths properties = PathTree.createPathTrees(Arrays.asList(paths), startIndex); } StringBuilder example = new StringBuilder(); example.append(GroovyConstants.BINDING_TARGET); example.append(" {\n"); int indentCount = 1; for (PathTree tree : properties) { InstanceBuilderCode.appendBuildProperties(example, indentCount, tree, parent, BUILDER_USE_BRACKETS); } example.append("}"); return example.toString(); } return GroovyConstants.BINDING_TARGET + " = {}"; } }