/*******************************************************************************
* Copyright (c) 2009 Fraunhofer IWU 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:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.komma.sparql.ui.views;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.enilink.commons.iterator.IExtendedIterator;
import net.enilink.commons.ui.editor.AbstractEditorPart;
import net.enilink.commons.ui.editor.EditorWidgetFactory;
import net.enilink.commons.ui.editor.PageBook;
import net.enilink.commons.ui.jface.viewers.CComboViewer;
import net.enilink.commons.ui.progress.ProgressDistributor;
import net.enilink.commons.ui.progress.UiProgressMonitorWrapper;
import net.enilink.commons.util.Pair;
import net.enilink.commons.util.extensions.RegistryReader;
import net.enilink.komma.common.ui.assist.ContentProposals;
import net.enilink.komma.core.IBindings;
import net.enilink.komma.core.IBooleanResult;
import net.enilink.komma.core.IEntity;
import net.enilink.komma.core.IEntityManager;
import net.enilink.komma.core.IEntityManagerFactory;
import net.enilink.komma.core.IGraphResult;
import net.enilink.komma.core.INamespace;
import net.enilink.komma.core.IQuery;
import net.enilink.komma.core.IStatement;
import net.enilink.komma.core.ITupleResult;
import net.enilink.komma.core.IUnitOfWork;
import net.enilink.komma.edit.assist.ParboiledProposalProvider;
import net.enilink.komma.edit.ui.assist.JFaceProposalProvider;
import net.enilink.komma.model.IModel;
import net.enilink.komma.model.IModelAware;
import net.enilink.komma.parser.sparql.Sparql11Parser;
import net.enilink.komma.query.SparqlBuilder;
import net.enilink.komma.sparql.ui.SparqlUI;
import net.enilink.komma.sparql.ui.assist.SparqlProposals;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.wizard.ProgressMonitorPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.Section;
import org.parboiled.Parboiled;
class SparqlPart extends AbstractEditorPart {
private class LoadResultsJob extends FinishInUIJob {
ProgressDistributor progressDistributor;
String[] columnNames;
List<Object[]> data;
IEntityManagerFactory managerFactory;
String sparql;
List<IEntity> selected;
Set<INamespace> namespaces;
private LoadResultsJob(IEntityManagerFactory managerFactory,
List<IEntity> selected, Set<INamespace> namespaces,
String sparql) {
super("Evaluating SPARQL"); //$NON-NLS-1$
this.managerFactory = managerFactory;
this.selected = selected;
this.namespaces = namespaces;
this.sparql = sparql;
}
@Override
public void finishInUI(IStatus status) {
if (status.isOK()) {
resultArea.setData(namespaces, columnNames, data);
} else {
resultArea.setError(status);
}
}
@Override
protected void canceling() {
if (progressDistributor != null) {
progressDistributor.removeMonitor(uiProgressMonitor);
}
}
@Override
public IStatus runAsync(IProgressMonitor monitor) {
progressDistributor = new ProgressDistributor();
progressDistributor.addMonitor(monitor);
progressDistributor.addMonitor(uiProgressMonitor);
progressDistributor.beginTask("Loading results",
IProgressMonitor.UNKNOWN);
Set<IModel> models = new HashSet<IModel>();
for (IEntity entity : selected) {
if (entity instanceof IModel) {
models.add((IModel) entity);
} else if (entity instanceof IModelAware) {
models.add(((IModelAware) entity).getModel());
}
}
IUnitOfWork uow;
IEntityManager managerForQuery;
if (models.size() != 1) {
if (this.managerFactory == null) {
return Status.CANCEL_STATUS;
}
uow = this.managerFactory.getUnitOfWork();
uow.begin();
managerForQuery = this.managerFactory.get();
} else {
IModel model = models.iterator().next();
uow = model.getModelSet().getUnitOfWork();
uow.begin();
managerForQuery = model.getManager();
}
try {
IQuery<?> query = managerForQuery.createQuery(sparql);
if (selected.size() > 0) {
int i = 0;
for (Object entity : selected) {
if (i == 0) {
query.setParameter("selected", entity);
}
query.setParameter("selected" + (++i), entity);
}
}
IExtendedIterator<?> result = query.evaluate();
if (result instanceof ITupleResult<?>) {
columnNames = ((ITupleResult<?>) result).getBindingNames()
.toArray(
new String[((ITupleResult<?>) result)
.getBindingNames().size()]);
data = new ArrayList<Object[]>();
while (result.hasNext()) {
Object value = result.next();
Object[] row = new Object[columnNames.length];
for (int i = 0; i < columnNames.length; i++) {
row[i] = value instanceof IBindings<?> ? ((IBindings<?>) value)
.get(columnNames[i]) : value;
}
data.add(row);
}
result.close();
} else if (result instanceof IBooleanResult) {
columnNames = new String[] { "result" };
data = new ArrayList<Object[]>();
data.add(new Object[] { ((IBooleanResult) result)
.asBoolean() });
} else if (result instanceof IGraphResult) {
columnNames = new String[] { "subject", "predicate",
"object" };
data = new ArrayList<Object[]>();
while (result.hasNext()) {
IStatement stmt = ((IGraphResult) result).next();
data.add(new Object[] { stmt.getSubject(),
stmt.getPredicate(), stmt.getObject() });
}
result.close();
}
} catch (Exception e) {
return new Status(IStatus.ERROR, SparqlUI.PLUGIN_ID,
"Error executing query", e);
} finally {
uow.end();
progressDistributor.done();
}
return Status.OK_STATUS;
}
}
class StatusLine {
EditorWidgetFactory widgetFactory;
Label rowsLabel;
StatusLine(EditorWidgetFactory widgetFactory) {
this.widgetFactory = widgetFactory;
}
void createContents(Composite parent) {
parent.setLayout(new GridLayout(2, false));
Composite labelComposite = widgetFactory.createComposite(parent);
labelComposite.setLayout(new RowLayout());
labelComposite.setLayoutData(new GridData(SWT.BEGINNING,
SWT.DEFAULT, false, false));
rowsLabel = widgetFactory.createLabel(labelComposite, ""); //$NON-NLS-1$
}
void setInfo(int rowCount) {
rowsLabel.setText(rowCount + " " //$NON-NLS-1$
+ (rowCount == 1 ? "result" : "results")); //$NON-NLS-1$ //$NON-NLS-2$
rowsLabel.getParent().layout(true);
}
}
LoadResultsJob loadJob;
IProgressMonitor uiProgressMonitor;
Composite parent;
class ResultArea {
PageBook pageBook, resultViewerContent;
StatusLine statusLine;
ResultViewerWrapper[] resultViewers;
Composite progressComposite, dataComposite, errorComposite;
ResultArea(Composite composite) {
composite.setLayout(new FillLayout());
pageBook = getWidgetFactory().createPageBook(composite, SWT.NONE);
progressComposite = pageBook.createPage("progress");
progressComposite.setLayout(new GridLayout(1, false));
ProgressMonitorPart progressViewer = new ProgressMonitorPart(
progressComposite, null);
progressViewer.setData(EditorWidgetFactory.KEY_DRAW_BORDER,
EditorWidgetFactory.TEXT_BORDER);
progressViewer.setLayoutData(new GridData(SWT.FILL, SWT.CENTER,
true, true));
uiProgressMonitor = new UiProgressMonitorWrapper(progressViewer,
getShell().getDisplay());
dataComposite = pageBook.createPage("data");
dataComposite.setLayout(new GridLayout(1, false));
List<IResultViewer> viewers = getResultViewers();
resultViewers = new ResultViewerWrapper[viewers.size()];
int i = 0;
for (IResultViewer viewer : viewers) {
resultViewers[i++] = new ResultViewerWrapper(viewer);
}
if (resultViewers.length > 1) {
Composite tabsComposite = getWidgetFactory().createComposite(
dataComposite);
tabsComposite.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT,
true, false));
createTabs(tabsComposite);
}
resultViewerContent = getWidgetFactory().createPageBook(
dataComposite, SWT.NONE);
resultViewerContent.setLayoutData(new GridData(SWT.FILL, SWT.FILL,
true, true));
Composite statusComposite = getWidgetFactory().createComposite(
dataComposite);
statusComposite.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT,
true, false));
statusLine = new StatusLine(getWidgetFactory());
statusLine.createContents(statusComposite);
pageBook.showEmptyPage();
}
public void setError(IStatus status) {
pageBook.showEmptyPage();
}
private void createTabs(Composite composite) {
composite.setLayout(new RowLayout());
for (final ResultViewerWrapper wrapper : resultViewers) {
wrapper.link = getWidgetFactory().createHyperlink(composite,
wrapper.viewer.getName(), SWT.NONE);
wrapper.link.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
wrapper.show();
}
});
}
}
void startLoading() {
pageBook.showPage("progress");
}
List<IResultViewer> getResultViewers() {
final List<IResultViewer> resultViewers = new ArrayList<IResultViewer>();
RegistryReader reader = new RegistryReader(SparqlUI.PLUGIN_ID,
"resultViewers") {
@Override
protected boolean readElement(IConfigurationElement element) {
if (!"resultViewer".equals(element.getName())) {
return false;
}
String className = element.getAttribute("class");
if (className == null || className.trim().isEmpty()) {
logMissingAttribute(element, "class");
return true;
}
try {
Object viewerObj = element
.createExecutableExtension("class");
if (!(viewerObj instanceof IResultViewer)) {
logError(element,
"Result viewer does not implement expected interface "
+ IResultViewer.class.getName());
} else {
resultViewers.add((IResultViewer) viewerObj);
}
} catch (CoreException e) {
logError(element, e.getMessage());
}
return true;
}
};
reader.readRegistry();
return resultViewers;
}
private void setData(Set<INamespace> namespaces, String[] columnNames,
Collection<Object[]> data) {
statusLine.setInfo(data.size());
for (ResultViewerWrapper wrapper : resultViewers) {
wrapper.setData(namespaces, columnNames, data);
}
if (resultViewerContent.getCurrentPage() == null
&& resultViewers.length > 0) {
resultViewers[0].show();
}
pageBook.showPage("data");
}
class ResultViewerWrapper {
Composite composite;
IResultViewer viewer;
Hyperlink link;
int limit;
Set<INamespace> namespaces;
String[] columnNames;
Collection<Object[]> data;
boolean dataChanged = false;
ResultViewerWrapper(IResultViewer viewer) {
this.viewer = viewer;
}
void show() {
if (!resultViewerContent.hasPage(viewer)) {
composite = resultViewerContent.createPage(viewer);
viewer.createContents(getWidgetFactory(), composite);
}
resultViewerContent.showPage(viewer);
applyData();
}
void setData(Set<INamespace> namespaces, String[] columnNames,
Collection<Object[]> data) {
dataChanged = true;
this.namespaces = namespaces;
this.columnNames = columnNames;
this.data = data;
applyData();
}
private void applyData() {
if (composite != null
&& resultViewerContent.getCurrentPage() == composite) {
viewer.setData(namespaces, columnNames, data);
dataChanged = false;
}
}
}
}
ResultArea resultArea;
Text queryText;
@Override
public void createContents(Composite parent) {
this.parent = parent;
parent.setLayout(new FillLayout());
Section section = getWidgetFactory().createSection(parent,
Section.TITLE_BAR | Section.EXPANDED);
section.setText("SPARQL"); //$NON-NLS-1$
getWidgetFactory().createCompositeSeparator(section);
Composite client = getWidgetFactory().createComposite(section);
getWidgetFactory().paintBordersFor(client);
section.setClient(client);
client.setLayout(new FillLayout());
SashForm sash = new SashForm(client, SWT.VERTICAL);
Composite queryComposite = getWidgetFactory().createComposite(sash);
queryComposite.setLayout(new GridLayout(2, false));
// Selection-Field for namespaces
final CCombo combo = getWidgetFactory().createCCombo(queryComposite);
GridData gridData = new GridData(SWT.FILL, SWT.BEGINNING, true, false);
combo.setLayoutData(gridData);
final CComboViewer comboViewer = new CComboViewer(combo);
comboViewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(Object element) {
return ((Pair<?, ?>) element).getFirst() + ": <"
+ ((Pair<?, ?>) element).getSecond() + ">";
}
});
combo.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
}
@Override
public void focusGained(FocusEvent e) {
combo.removeAll();
for (INamespace namespace : getContextNamespaces()) {
comboViewer.add(new Pair<String, String>(namespace
.getPrefix(), namespace.getURI().toString()));
}
}
});
Hyperlink addPrefixLink = getWidgetFactory().createHyperlink(
queryComposite, "Add", SWT.NONE);
addPrefixLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
Pair<?, ?> prefix = (Pair<?, ?>) ((IStructuredSelection) comboViewer
.getSelection()).getFirstElement();
if (prefix != null) {
queryText.setText(("PREFIX " + prefix.getFirst() + ": <"
+ prefix.getSecond() + ">\r\n" + queryText
.getText()).replace("\r\n", "\n"));
}
}
});
queryText = getWidgetFactory().createText(queryComposite, "",
SWT.MULTI | SWT.V_SCROLL);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
queryText.setLayoutData(gridData);
queryText.append("select ?p ?o\nwhere {\n\t?selected ?p ?o\n}\n");
MenuManager menuManager = new MenuManager();
menuManager.add(new Action("Format") {
@Override
public void run() {
try {
queryText.setText(new SparqlBuilder(queryText.getText())
.toString());
} catch (Exception e) {
// ignore
}
}
});
queryText.setMenu(menuManager.createContextMenu(queryText));
ContentProposalAdapter proposalAdapter = ContentProposals
.enableContentProposal(queryText, JFaceProposalProvider
.wrap(new ParboiledProposalProvider(Parboiled
.createParser(Sparql11Parser.class).Query(),
new SparqlProposals())), null);
proposalAdapter.setAutoActivationDelay(1000);
proposalAdapter.setPopupSize(new Point(200, 120));
Button button = getWidgetFactory().createButton(queryComposite, "Run",
SWT.PUSH);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String sparql = queryText.getText();
loadResultData(sparql);
}
});
button.setLayoutData(new GridData(SWT.NONE, SWT.BOTTOM, false, false));
Composite resultsComposite = getWidgetFactory().createComposite(sash);
resultsComposite.setLayout(new FillLayout());
resultArea = new ResultArea(resultsComposite);
sash.setWeights(new int[] { 40, 60 });
}
protected Set<INamespace> getContextNamespaces() {
Set<IEntityManager> managers = new HashSet<>();
for (IEntity entity : getSelectedEntities()) {
managers.add(entity.getEntityManager());
}
Set<INamespace> namespaces = new HashSet<>();
if (managers.isEmpty()) {
IEntityManagerFactory managerFactory = (IEntityManagerFactory) getForm()
.getAdapter(IEntityManagerFactory.class);
if (managerFactory != null) {
try (IEntityManager manager = managerFactory.get()) {
namespaces.addAll(manager.getNamespaces().toList());
}
}
} else {
for (IEntityManager manager : managers) {
namespaces.addAll(manager.getNamespaces().toList());
}
}
return namespaces;
}
protected List<IEntity> getSelectedEntities() {
List<IEntity> selectedEntities = new ArrayList<>();
Object formInput = getForm().getInput();
if (formInput instanceof IStructuredSelection) {
for (Object selected : ((IStructuredSelection) formInput).toList()) {
if (selected instanceof IEntity) {
selectedEntities.add((IEntity) selected);
}
}
}
return selectedEntities;
}
public void loadResultData(String sparql) {
if (loadJob != null) {
loadJob.cancel();
}
resultArea.startLoading();
// add common prefix declaration to query
Set<String> prefixes = new HashSet<>();
Matcher prefixMatcher = Pattern.compile("prefix\\s+([^:]+)\\s*:",
Pattern.CASE_INSENSITIVE).matcher(sparql);
while (prefixMatcher.find()) {
prefixes.add(prefixMatcher.group(1));
}
StringBuilder sb = new StringBuilder();
Set<INamespace> namespaces = getContextNamespaces();
for (INamespace namespace : namespaces) {
if (!prefixes.contains(namespace.getPrefix())) {
sb.append("PREFIX ").append(namespace.getPrefix())
.append(": <").append(namespace.getURI()).append(">\n");
}
}
sb.append(sparql);
IEntityManagerFactory managerFactory = (IEntityManagerFactory) getForm()
.getAdapter(IEntityManagerFactory.class);
loadJob = new LoadResultsJob(managerFactory, getSelectedEntities(),
namespaces, sb.toString());
loadJob.setUser(true);
loadJob.schedule();
}
}