Merge remote-tracking branch 'origin/releases/5.2.x' into releases/6.0.x
diff --git a/org.eclipse.scout.rt.client.test/src/test/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/SmartFieldTest.java b/org.eclipse.scout.rt.client.test/src/test/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/SmartFieldTest.java
index 518fb01..f72d676 100644
--- a/org.eclipse.scout.rt.client.test/src/test/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/SmartFieldTest.java
+++ b/org.eclipse.scout.rt.client.test/src/test/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/SmartFieldTest.java
@@ -16,6 +16,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
@@ -26,6 +28,8 @@
import org.eclipse.scout.rt.client.testenvironment.TestEnvironmentClientSession;
import org.eclipse.scout.rt.client.ui.action.menu.AbstractMenu;
import org.eclipse.scout.rt.client.ui.action.menu.IMenu;
+import org.eclipse.scout.rt.client.ui.basic.tree.ITree;
+import org.eclipse.scout.rt.client.ui.basic.tree.ITreeNode;
import org.eclipse.scout.rt.client.ui.form.AbstractForm;
import org.eclipse.scout.rt.client.ui.form.fields.button.AbstractCloseButton;
import org.eclipse.scout.rt.client.ui.form.fields.groupbox.AbstractGroupBox;
@@ -33,6 +37,9 @@
import org.eclipse.scout.rt.platform.BeanMetaData;
import org.eclipse.scout.rt.platform.IBean;
import org.eclipse.scout.rt.platform.Order;
+import org.eclipse.scout.rt.platform.job.IBlockingCondition;
+import org.eclipse.scout.rt.platform.job.Jobs;
+import org.eclipse.scout.rt.platform.status.IStatus;
import org.eclipse.scout.rt.platform.util.Assertions;
import org.eclipse.scout.rt.shared.data.basic.FontSpec;
import org.eclipse.scout.rt.shared.services.lookup.ILookupCall;
@@ -309,6 +316,99 @@
}
@Test
+ public void testHierarchical_UIFacade() {
+ StyleField f = m_styleField;
+ f.setBrowseHierarchy(true);
+ f.getUIFacade().acceptProposalFromUI("Red", false, false);
+ assertRedFieldStyle(f);
+ f.getUIFacade().acceptProposalFromUI("Empty", false, false);
+ assertDefaultFieldStyle(f);
+ f.getUIFacade().acceptProposalFromUI("Yellow", false, false);
+ assertYellowFieldStyle(f);
+ f.getUIFacade().acceptProposalFromUI(null, false, false);
+ assertDefaultFieldStyle(f);
+ }
+
+ @Test
+ public void testHierarchicalOpenProposal() {
+ StyleField f = m_styleField;
+ f.setBrowseHierarchy(true);
+ f.setValue(10L);
+ f.getUIFacade().openProposalChooserFromUI("Red", true);
+ waitForProposalResult(IProposalChooser.PROP_SEARCH_RESULT);
+
+ //single result
+ assertTrue(m_styleField.isProposalChooserRegistered());
+ assertEquals("Red", m_styleField.getDisplayText());
+
+ // verifies tree is loaded containing a single node
+ @SuppressWarnings("unchecked")
+ TreeProposalChooser<Long> treeProposalChooser = (TreeProposalChooser<Long>) f.getProposalChooser();
+ ITree tree = treeProposalChooser.getModel();
+ ITreeNode rootNode = tree.getRootNode();
+ assertEquals(5, rootNode.getChildNodes().size());
+ assertEquals("Red", rootNode.getChildNode(0).getCell().getText());
+ //current value should be selected
+ assertTrue(rootNode.getChildNode(0).isSelectedNode());
+ }
+
+ @Test
+ public void testHierarchicalBrowse() {
+ StyleField f = m_styleField;
+ f.setBrowseHierarchy(true);
+
+ m_styleField.getUIFacade().openProposalChooserFromUI("*", false);
+
+ waitForProposalResult(IProposalChooser.PROP_SEARCH_RESULT);
+ //single result
+ assertTrue(m_styleField.isProposalChooserRegistered());
+ assertEquals("*", m_styleField.getDisplayText());
+
+ // verifies tree is loaded containing a single node
+ @SuppressWarnings("unchecked")
+ TreeProposalChooser<Long> treeProposalChooser = (TreeProposalChooser<Long>) f.getProposalChooser();
+ ITree tree = treeProposalChooser.getModel();
+ ITreeNode rootNode = tree.getRootNode();
+ assertEquals(5, rootNode.getChildNodes().size());
+ assertEquals("Red", rootNode.getChildNode(0).getCell().getText());
+ }
+
+ @Test
+ public void testHierarchyNoResult() {
+ StyleField f = m_styleField;
+ f.setBrowseHierarchy(true);
+
+ m_styleField.getUIFacade().openProposalChooserFromUI("unknown", false);
+
+ waitForProposalResult(IProposalChooser.PROP_STATUS);
+ //single result
+ assertTrue(m_styleField.isProposalChooserRegistered());
+ assertEquals("unknown", m_styleField.getDisplayText());
+
+ // verifies tree is loaded containing a single node
+ @SuppressWarnings("unchecked")
+ TreeProposalChooser<Long> treeProposalChooser = (TreeProposalChooser<Long>) f.getProposalChooser();
+ ITree tree = treeProposalChooser.getModel();
+ ITreeNode rootNode = tree.getRootNode();
+ assertEquals(0, rootNode.getChildNodes().size());
+ }
+
+ @Test
+ public void testThrowingLookupCall() {
+ StyleField f = m_styleField;
+ f.setBrowseHierarchy(true);
+ ((StyleLookupCall) f.getLookupCall()).allowLookup(false);
+ m_styleField.getUIFacade().openProposalChooserFromUI("Red", false);
+ waitForProposalResult(IProposalChooser.PROP_STATUS);
+ @SuppressWarnings("unchecked")
+ TreeProposalChooser<Long> treeProposalChooser = ((TreeProposalChooser<Long>) f.getProposalChooser());
+ ITree tree = treeProposalChooser.getModel();
+ ITreeNode rootNode = tree.getRootNode();
+ assertEquals(0, rootNode.getChildNodes().size());
+ assertEquals(IStatus.ERROR, treeProposalChooser.getStatus().getSeverity());
+ }
+
+ @Test
public void testStyle_AcceptProposal() throws Throwable {
StyleField f = m_styleField;
f.acceptProposal(createRedLookupRow());
@@ -451,4 +551,17 @@
// Yield the current model job permit, so that the lookup rows can be written into the model.
ModelJobs.yield();
}
+
+ private void waitForProposalResult(String property) {
+ final IBlockingCondition bc = Jobs.newBlockingCondition(true);
+ IProposalChooser<?, ?> chooser = m_styleField.getProposalChooser();
+ chooser.addPropertyChangeListener(property, new PropertyChangeListener() {
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ bc.setBlocking(false);
+ }
+ });
+ bc.waitFor();
+ }
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/basic/tree/AbstractTreeNodeBuilder.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/basic/tree/AbstractTreeNodeBuilder.java
index dfa19dd..16c96aa 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/basic/tree/AbstractTreeNodeBuilder.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/basic/tree/AbstractTreeNodeBuilder.java
@@ -11,7 +11,6 @@
package org.eclipse.scout.rt.client.ui.basic.tree;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,7 +32,7 @@
return createTreeNode(new LookupRow<LOOKUP_ROW_TYPE>(primaryKey, text), nodeStatus, markChildrenLoaded);
}
- public List<ITreeNode> createTreeNodes(Collection<? extends ILookupRow<LOOKUP_ROW_TYPE>> lookupRows, int nodeStatus, boolean markChildrenLoaded) {
+ public List<ITreeNode> createTreeNodes(List<? extends ILookupRow<LOOKUP_ROW_TYPE>> lookupRows, int nodeStatus, boolean markChildrenLoaded) {
ArrayList<ITreeNode> rootNodes = new ArrayList<>();
HashMap<Object, ITreeNode> nodeMap = new HashMap<>();
HashMap<LOOKUP_ROW_TYPE, ArrayList<ITreeNode>> parentChildMap = new HashMap<>();
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistField.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistField.java
index 81d8fbf..6dab23c 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistField.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistField.java
@@ -1020,10 +1020,16 @@
@Override
public void doSearch(String text, boolean selectCurrentValue, boolean synchronous) {
+ IContentAssistSearchParam<LOOKUP_KEY> param = ContentAssistSearchParam.createTextParam(toSearchText(text), selectCurrentValue);
+ doSearch(param, synchronous);
+ }
+
+ @Override
+ public void doSearch(IContentAssistSearchParam<LOOKUP_KEY> param, boolean synchronous) {
if (isProposalChooserRegistered()) {
getProposalChooser().setStatus(new Status(ScoutTexts.get("searchingProposals"), IStatus.OK));
}
- getLookupRowFetcher().update(text, selectCurrentValue, synchronous);
+ getLookupRowFetcher().update(param, synchronous);
}
// blocking lookups
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistFieldLookupRowFetcher.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistFieldLookupRowFetcher.java
index 7c7cb4e..3a1051c 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistFieldLookupRowFetcher.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractContentAssistFieldLookupRowFetcher.java
@@ -18,7 +18,7 @@
public abstract class AbstractContentAssistFieldLookupRowFetcher<LOOKUP_KEY> implements IContentAssistFieldLookupRowFetcher<LOOKUP_KEY> {
- private BasicPropertySupport propertySupport;
+ private final BasicPropertySupport propertySupport;
private final IContentAssistField<?, LOOKUP_KEY> m_contentAssistField;
public AbstractContentAssistFieldLookupRowFetcher(IContentAssistField<?, LOOKUP_KEY> contentAssistField) {
@@ -78,7 +78,8 @@
@Override
public IContentAssistFieldDataFetchResult<LOOKUP_KEY> newResult(String searchText, boolean selectCurrentValue) {
- return new ContentAssistFieldDataFetchResult<LOOKUP_KEY>(Collections.<ILookupRow<LOOKUP_KEY>> emptyList(), null, searchText, selectCurrentValue);
+ IContentAssistSearchParam<LOOKUP_KEY> param = ContentAssistSearchParam.createTextParam(searchText, selectCurrentValue);
+ return new ContentAssistFieldDataFetchResult<LOOKUP_KEY>(Collections.<ILookupRow<LOOKUP_KEY>> emptyList(), null, param);
}
protected void setResult(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
@@ -89,7 +90,7 @@
@Override
public String getLastSearchText() {
if (getResult() != null) {
- return getResult().getSearchText();
+ return getResult().getSearchParam().getSearchText();
}
return null;
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractMixedSmartField.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractMixedSmartField.java
index 01854bf..9c69149 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractMixedSmartField.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractMixedSmartField.java
@@ -135,7 +135,8 @@
}
String searchText = toSearchText(text);
- getLookupRowFetcher().update(searchText, false, true);
+ IContentAssistSearchParam<LOOKUP_KEY> param = ContentAssistSearchParam.createTextParam(searchText, false);
+ getLookupRowFetcher().update(param, true);
List<? extends ILookupRow<LOOKUP_KEY>> lookupRows = getLookupRowFetcher().getResult().getLookupRows();
if (lookupRows == null || lookupRows.size() == 0) {
setValidationError(text, TEXTS.get("SmartFieldCannotComplete", text), NO_RESULTS_ERROR_CODE);
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractProposalChooser.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractProposalChooser.java
index 8afcd49..61ef4c2 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractProposalChooser.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/AbstractProposalChooser.java
@@ -10,10 +10,17 @@
******************************************************************************/
package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
+import java.util.List;
+
+import org.eclipse.scout.rt.platform.exception.ProcessingException;
import org.eclipse.scout.rt.platform.reflect.AbstractPropertyObserver;
import org.eclipse.scout.rt.platform.reflect.ConfigurationUtility;
import org.eclipse.scout.rt.platform.status.IStatus;
+import org.eclipse.scout.rt.platform.status.Status;
+import org.eclipse.scout.rt.platform.util.CollectionUtility;
import org.eclipse.scout.rt.platform.util.TriState;
+import org.eclipse.scout.rt.platform.util.concurrent.FutureCancelledException;
+import org.eclipse.scout.rt.shared.ScoutTexts;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
public abstract class AbstractProposalChooser<T, LOOKUP_KEY> extends AbstractPropertyObserver implements IProposalChooser<T, LOOKUP_KEY> {
@@ -103,7 +110,7 @@
public String getSearchText() {
IContentAssistFieldDataFetchResult<LOOKUP_KEY> searchResult = getSearchResult();
if (searchResult != null) {
- return searchResult.getSearchText();
+ return searchResult.getSearchParam().getSearchText();
}
return null;
}
@@ -183,4 +190,52 @@
return m_contentAssistField;
}
+ protected void updateStatus(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
+ if (result != null && result.getException() instanceof FutureCancelledException) {
+ return;
+ }
+
+ List<? extends ILookupRow<LOOKUP_KEY>> rows = null;
+ Throwable exception = null;
+ String searchText = null;
+ if (result != null) {
+ rows = result.getLookupRows();
+ exception = result.getException();
+ searchText = result.getSearchParam().getSearchText();
+ }
+ if (rows == null) {
+ rows = CollectionUtility.emptyArrayList();
+ }
+ String statusText = null;
+ int severity = IStatus.INFO;
+ if (exception != null) {
+ if (exception instanceof ProcessingException) {
+ statusText = ((ProcessingException) exception).getStatus().getMessage();
+ }
+ else {
+ statusText = exception.getMessage();
+ }
+ severity = IStatus.ERROR;
+ }
+ else if (rows.size() <= 0) {
+ if (getContentAssistField().getWildcard().equals(searchText)) {
+ statusText = ScoutTexts.get("SmartFieldNoDataFound");
+ }
+ else {
+ statusText = ScoutTexts.get("SmartFieldCannotComplete", (searchText == null) ? ("") : (searchText));
+ }
+ severity = IStatus.WARNING;
+ }
+ else if (rows.size() > m_contentAssistField.getBrowseMaxRowCount()) {
+ statusText = ScoutTexts.get("SmartFieldMoreThanXRows", "" + m_contentAssistField.getBrowseMaxRowCount());
+ severity = IStatus.INFO;
+ }
+ if (statusText != null) {
+ setStatus(new Status(statusText, severity));
+ }
+ else {
+ setStatus(null);
+ }
+ }
+
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetchResult.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetchResult.java
index cb3e04f..6c013a6 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetchResult.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetchResult.java
@@ -12,50 +12,43 @@
import java.util.List;
+import org.eclipse.scout.rt.platform.util.ToStringBuilder;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
public class ContentAssistFieldDataFetchResult<LOOKUP_KEY> implements IContentAssistFieldDataFetchResult<LOOKUP_KEY> {
- private final List<? extends ILookupRow<LOOKUP_KEY>> m_lookupRows;
- private final RuntimeException m_processingException;
- private final String m_searchText;
- private final boolean m_selectCurrentValue;
+ private final IContentAssistSearchParam<LOOKUP_KEY> m_searchParam;
+ private final List<ILookupRow<LOOKUP_KEY>> m_lookupRows;
+ private final Throwable m_processingException;
- public ContentAssistFieldDataFetchResult(List<? extends ILookupRow<LOOKUP_KEY>> rows, RuntimeException failed, String searchText, boolean selectCurrentValue) {
+ public ContentAssistFieldDataFetchResult(List<ILookupRow<LOOKUP_KEY>> rows, Throwable failed, IContentAssistSearchParam<LOOKUP_KEY> searchParam) {
m_lookupRows = rows;
m_processingException = failed;
- m_searchText = searchText;
- m_selectCurrentValue = selectCurrentValue;
+ m_searchParam = searchParam;
}
@Override
- public List<? extends ILookupRow<LOOKUP_KEY>> getLookupRows() {
+ public List<ILookupRow<LOOKUP_KEY>> getLookupRows() {
return m_lookupRows;
}
@Override
- public RuntimeException getException() {
+ public Throwable getException() {
return m_processingException;
}
@Override
- public String getSearchText() {
- return m_searchText;
- }
-
- @Override
- public boolean isSelectCurrentValue() {
- return m_selectCurrentValue;
+ public IContentAssistSearchParam<LOOKUP_KEY> getSearchParam() {
+ return m_searchParam;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
+ result = prime * result + ((m_searchParam == null) ? 0 : m_searchParam.hashCode());
result = prime * result + ((m_lookupRows == null) ? 0 : m_lookupRows.hashCode());
result = prime * result + ((m_processingException == null) ? 0 : m_processingException.hashCode());
- result = prime * result + ((m_searchText == null) ? 0 : m_searchText.hashCode());
- result = prime * result + (m_selectCurrentValue ? 1231 : 1237);
return result;
}
@@ -71,6 +64,14 @@
return false;
}
ContentAssistFieldDataFetchResult other = (ContentAssistFieldDataFetchResult) obj;
+ if (m_searchParam == null) {
+ if (other.m_searchParam != null) {
+ return false;
+ }
+ }
+ else if (!m_searchParam.equals(other.m_searchParam)) {
+ return false;
+ }
if (m_lookupRows == null) {
if (other.m_lookupRows != null) {
return false;
@@ -87,27 +88,16 @@
else if (!m_processingException.equals(other.m_processingException)) {
return false;
}
- if (m_searchText == null) {
- if (other.m_searchText != null) {
- return false;
- }
- }
- else if (!m_searchText.equals(other.m_searchText)) {
- return false;
- }
- if (m_selectCurrentValue != other.m_selectCurrentValue) {
- return false;
- }
return true;
}
@Override
public String toString() {
- String s = getClass().getSimpleName() + " {";
- s += "[searchText: " + (m_searchText == null ? "null" : "'" + m_searchText + "'") + "], ";
- s += "[lookupRows: " + (getLookupRows() == null ? "null" : getLookupRows().size() + " rows") + "], ";
- s += "[selectCurrentValue: " + isSelectCurrentValue() + "], ";
- s += "[processingException: " + getException() + "]}";
- return s;
+ return new ToStringBuilder(this)
+ .attr(m_searchParam)
+ .attr("lookupRows", m_lookupRows)
+ .attr("exception", m_processingException)
+ .toString();
}
+
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetcher.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetcher.java
index 03dd6ee..c9ee934 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetcher.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldDataFetcher.java
@@ -10,6 +10,7 @@
******************************************************************************/
package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
+import java.util.ArrayList;
import java.util.List;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
@@ -23,7 +24,8 @@
}
@Override
- public void update(final String searchText, boolean selectCurrentValue, boolean synchronous) {
+ public void update(IContentAssistSearchParam<LOOKUP_KEY> query, boolean synchronous) {
+ String searchText = query.getSearchText();
String text = searchText;
if (text == null) {
text = "";
@@ -31,7 +33,7 @@
final String textNonNull = text;
final int maxCount = getContentAssistField().getBrowseMaxRowCount();
- final ILookupRowFetchedCallback<LOOKUP_KEY> callback = new P_LookupCallDataCallback(searchText, selectCurrentValue);
+ final ILookupRowFetchedCallback<LOOKUP_KEY> callback = new P_LookupCallDataCallback(query);
final int maxRowCount = (maxCount > 0 ? maxCount + 1 : 0);
if (synchronous) {
@@ -58,23 +60,22 @@
}
private class P_LookupCallDataCallback implements ILookupRowFetchedCallback<LOOKUP_KEY> {
- private String m_searchText;
- private boolean m_selectCurrentValue;
+ private final IContentAssistSearchParam<LOOKUP_KEY> m_param;
- private P_LookupCallDataCallback(String searchText, boolean selectCurrentValue) {
- m_searchText = searchText;
- m_selectCurrentValue = selectCurrentValue;
+ private P_LookupCallDataCallback(IContentAssistSearchParam<LOOKUP_KEY> param) {
+ m_param = param;
setResult(null);
}
@Override
public void onSuccess(List<? extends ILookupRow<LOOKUP_KEY>> rows) {
- setResult(new ContentAssistFieldDataFetchResult<>(rows, null, m_searchText, m_selectCurrentValue));
+ setResult(new ContentAssistFieldDataFetchResult<>(new ArrayList<ILookupRow<LOOKUP_KEY>>(rows), null, m_param));
}
@Override
public void onFailure(RuntimeException exception) {
- setResult(new ContentAssistFieldDataFetchResult<LOOKUP_KEY>(null, exception, m_searchText, m_selectCurrentValue));
+ setResult(new ContentAssistFieldDataFetchResult<LOOKUP_KEY>(null, exception, m_param));
}
}
+
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldUIFacade.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldUIFacade.java
index ecd8334..5123171 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldUIFacade.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistFieldUIFacade.java
@@ -43,7 +43,13 @@
m_field.clearProposal();
m_field.setDisplayText(text);
if (!StringUtility.equalsIgnoreNewLines(m_field.getLookupRowFetcher().getLastSearchText(), toSearchText(text))) {
- m_field.doSearch(text, false, false);
+ if (m_field.isBrowseLoadIncremental() && m_field.getWildcard().equals(toSearchText(text))) {
+ IContentAssistSearchParam<LOOKUP_KEY> searchParam = ContentAssistSearchParam.createParentParam(null, false);
+ m_field.doSearch(searchParam, false);
+ }
+ else {
+ m_field.doSearch(text, false, false);
+ }
}
}
@@ -57,9 +63,15 @@
m_field.setDisplayText(text);
String searchText = toSearchText(text);
IProposalChooser<?, LOOKUP_KEY> proposalChooser = m_field.registerProposalChooserInternal();
- IContentAssistFieldDataFetchResult<LOOKUP_KEY> newResult = m_field.getLookupRowFetcher().newResult(toSearchText(searchText), selectCurrentValue);
+ IContentAssistFieldDataFetchResult<LOOKUP_KEY> newResult = m_field.getLookupRowFetcher().newResult(toSearchText(searchText), false);
proposalChooser.dataFetchedDelegate(newResult, m_field.getConfiguredBrowseMaxRowCount());
- m_field.doSearch(text, selectCurrentValue, false);
+ if (m_field.isBrowseLoadIncremental()) {
+ IContentAssistSearchParam<LOOKUP_KEY> searchParam = ContentAssistSearchParam.createParentParam(null, selectCurrentValue);
+ m_field.doSearch(searchParam, false);
+ }
+ else {
+ m_field.doSearch(text, selectCurrentValue, false);
+ }
}
@Override
@@ -105,7 +117,7 @@
boolean acceptByLookupRow = true;
String searchText = toSearchText(text);
String lastSearchText = m_field.getProposalChooser().getSearchText();
- if (!lastSearchText.equals(m_field.getWildcard())) {
+ if (lastSearchText != null && !lastSearchText.equals(m_field.getWildcard())) {
acceptByLookupRow = CompareUtility.equals(searchText, lastSearchText);
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistSearchParam.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistSearchParam.java
new file mode 100644
index 0000000..06b6e9f
--- /dev/null
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/ContentAssistSearchParam.java
@@ -0,0 +1,104 @@
+package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
+
+import org.eclipse.scout.rt.platform.util.ToStringBuilder;
+
+public class ContentAssistSearchParam<LOOKUP_KEY> implements IContentAssistSearchParam<LOOKUP_KEY> {
+
+ private final String m_searchText;
+ private final LOOKUP_KEY m_parentKey;
+ private final boolean m_isByParentSearch;
+ private final boolean m_selectCurrentValue;
+
+ ContentAssistSearchParam(String searchText, LOOKUP_KEY parentKey, boolean isByParentSearch, boolean selectCurrentValue) {
+ m_parentKey = parentKey;
+ m_selectCurrentValue = selectCurrentValue;
+ m_searchText = searchText;
+ m_isByParentSearch = isByParentSearch;
+ }
+
+ @Override
+ public String getSearchText() {
+ return m_searchText;
+ }
+
+ @Override
+ public LOOKUP_KEY getParentKey() {
+ return m_parentKey;
+ }
+
+ @Override
+ public boolean isSelectCurrentValue() {
+ return m_selectCurrentValue;
+ }
+
+ @Override
+ public boolean isByParentSearch() {
+ return m_isByParentSearch;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (m_isByParentSearch ? 1231 : 1237);
+ result = prime * result + ((m_parentKey == null) ? 0 : m_parentKey.hashCode());
+ result = prime * result + ((m_searchText == null) ? 0 : m_searchText.hashCode());
+ result = prime * result + (m_selectCurrentValue ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ContentAssistSearchParam other = (ContentAssistSearchParam) obj;
+ if (m_isByParentSearch != other.m_isByParentSearch) {
+ return false;
+ }
+ if (m_parentKey == null) {
+ if (other.m_parentKey != null) {
+ return false;
+ }
+ }
+ else if (!m_parentKey.equals(other.m_parentKey)) {
+ return false;
+ }
+ if (m_searchText == null) {
+ if (other.m_searchText != null) {
+ return false;
+ }
+ }
+ else if (!m_searchText.equals(other.m_searchText)) {
+ return false;
+ }
+ if (m_selectCurrentValue != other.m_selectCurrentValue) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .attr("searchText", m_searchText)
+ .attr("parentKey", m_parentKey)
+ .attr("selectCurrentValue", m_selectCurrentValue)
+ .toString();
+ }
+
+ public static <LOOKUP_KEY> IContentAssistSearchParam<LOOKUP_KEY> createTextParam(String searchText, boolean selectCurrentValue) {
+ return new ContentAssistSearchParam<LOOKUP_KEY>(searchText, null, false, selectCurrentValue);
+ }
+
+ public static <LOOKUP_KEY> IContentAssistSearchParam<LOOKUP_KEY> createParentParam(LOOKUP_KEY parentKey, boolean selectCurrentValue) {
+ return new ContentAssistSearchParam<LOOKUP_KEY>(null, parentKey, true, selectCurrentValue);
+ }
+
+}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/HierachycalContentAssistDataFetcher.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/HierachycalContentAssistDataFetcher.java
index 2bb615e..af3b4c6 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/HierachycalContentAssistDataFetcher.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/HierachycalContentAssistDataFetcher.java
@@ -10,19 +10,120 @@
******************************************************************************/
package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
-public class HierachycalContentAssistDataFetcher<LOOKUP_KEY> extends AbstractContentAssistFieldLookupRowFetcher<LOOKUP_KEY> {
+import java.util.List;
- /**
- * @param contentAssistField
- */
+import org.eclipse.scout.rt.client.context.ClientRunContexts;
+import org.eclipse.scout.rt.client.job.ModelJobs;
+import org.eclipse.scout.rt.platform.exception.IProcessingStatus;
+import org.eclipse.scout.rt.platform.exception.ProcessingException;
+import org.eclipse.scout.rt.platform.exception.VetoException;
+import org.eclipse.scout.rt.platform.job.IFuture;
+import org.eclipse.scout.rt.platform.job.JobInput;
+import org.eclipse.scout.rt.platform.job.Jobs;
+import org.eclipse.scout.rt.platform.util.StringUtility;
+import org.eclipse.scout.rt.platform.util.concurrent.IBiFunction;
+import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HierachycalContentAssistDataFetcher<LOOKUP_KEY> extends AbstractContentAssistFieldLookupRowFetcher<LOOKUP_KEY> {
+ private static final Logger LOG = LoggerFactory.getLogger(HierachycalContentAssistDataFetcher.class);
+
public HierachycalContentAssistDataFetcher(IContentAssistField<?, LOOKUP_KEY> contentAssistField) {
super(contentAssistField);
}
@Override
- public void update(String searchText, boolean selectCurrentValue, boolean synchronous) {
- // in case of hierarchical simply delegate the searchText
- setResult(newResult(searchText, selectCurrentValue));
+ public void update(IContentAssistSearchParam<LOOKUP_KEY> query, boolean blocking) {
+ IFuture<Void> fRes =
+ scheduleLookup(query)
+ .whenDoneSchedule(createResult(query), newJobInput())
+ .whenDoneSchedule(updateResult(), newModelJobInput());
+ if (blocking) {
+ LookupJobHelper.awaitDone(fRes);
+ }
+ }
+
+ private IBiFunction<List<ILookupRow<LOOKUP_KEY>>, Throwable, ContentAssistFieldDataFetchResult<LOOKUP_KEY>> createResult(final IContentAssistSearchParam<LOOKUP_KEY> fetchInfo) {
+ return new IBiFunction<List<ILookupRow<LOOKUP_KEY>>, Throwable, ContentAssistFieldDataFetchResult<LOOKUP_KEY>>() {
+
+ @Override
+ public ContentAssistFieldDataFetchResult<LOOKUP_KEY> apply(List<ILookupRow<LOOKUP_KEY>> rows, Throwable error) {
+ return new ContentAssistFieldDataFetchResult<>(rows, error, fetchInfo);
+ }
+ };
+ }
+
+ private IBiFunction<ContentAssistFieldDataFetchResult<LOOKUP_KEY>, Throwable, Void> updateResult() {
+ return new IBiFunction<ContentAssistFieldDataFetchResult<LOOKUP_KEY>, Throwable, Void>() {
+
+ @Override
+ public Void apply(ContentAssistFieldDataFetchResult<LOOKUP_KEY> result, Throwable error) {
+ if (result.getException() != null) {
+ logException(result.getException());
+ }
+ setResult(result);
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Exceptions are handled differently in smartfields (make sure it is logged)
+ */
+ protected void logException(Throwable e) {
+ if (e instanceof VetoException) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("", e);
+ }
+ else {
+ LOG.info("{}: {}", e.getClass().getSimpleName(), e.getMessage());
+ }
+ }
+ else if (e instanceof ProcessingException) {
+ switch (((ProcessingException) e).getStatus().getSeverity()) {
+ case IProcessingStatus.INFO:
+ case IProcessingStatus.OK:
+ LOG.info("", e);
+ break;
+ case IProcessingStatus.WARNING:
+ LOG.warn("", e);
+ break;
+ default:
+ LOG.error("", e);
+ break;
+ }
+ }
+ else {
+ LOG.error("", e);
+ }
+ }
+
+ private JobInput newModelJobInput() {
+ return ModelJobs.newInput(ClientRunContexts.copyCurrent())
+ .withExceptionHandling(null, true);
+ }
+
+ private JobInput newJobInput() {
+ return Jobs.newInput()
+ .withRunContext(ClientRunContexts.copyCurrent())
+ .withExceptionHandling(null, true);
+ }
+
+ protected IFuture<List<ILookupRow<LOOKUP_KEY>>> scheduleLookup(IContentAssistSearchParam<LOOKUP_KEY> query) {
+ if (query.isByParentSearch()) {
+ return getContentAssistField().callSubTreeLookupInBackground(query.getParentKey(), false);
+ }
+ else if (isTextLookup(query.getSearchText()) && !query.isSelectCurrentValue()) {
+ return getContentAssistField().callTextLookupInBackground(query.getSearchText(), true);
+ }
+ else {
+ return getContentAssistField().callBrowseLookupInBackground(null, true);
+ }
+ }
+
+ protected boolean isTextLookup(String searchText) {
+ return !StringUtility.isNullOrEmpty(searchText) && !getContentAssistField().getWildcard().equals(searchText);
}
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistField.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistField.java
index dae6066..8394eef 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistField.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistField.java
@@ -247,6 +247,8 @@
*/
void doSearch(String searchText, boolean selectCurrentValue, boolean synchronous);
+ void doSearch(IContentAssistSearchParam<LOOKUP_KEY> param, boolean synchronous);
+
// blocking lookups
/**
* Lookup rows by key using {@link ILookupCall#getDataByKey()}. Blocks until the result is available.
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldDataFetchResult.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldDataFetchResult.java
index 40969ba..1a95ea6 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldDataFetchResult.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldDataFetchResult.java
@@ -15,11 +15,11 @@
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
public interface IContentAssistFieldDataFetchResult<LOOKUP_KEY> {
- String getSearchText();
- RuntimeException getException();
+ Throwable getException();
- List<? extends ILookupRow<LOOKUP_KEY>> getLookupRows();
+ List<ILookupRow<LOOKUP_KEY>> getLookupRows();
- boolean isSelectCurrentValue();
+ IContentAssistSearchParam<LOOKUP_KEY> getSearchParam();
+
}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldLookupRowFetcher.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldLookupRowFetcher.java
index 19a35e7..5c6f2bf 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldLookupRowFetcher.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistFieldLookupRowFetcher.java
@@ -38,15 +38,7 @@
*/
void removePropertyChangeListener(String propertyName, PropertyChangeListener listener);
- /**
- * @param searchText
- * @param selectCurrentValue
- * select the current proposal value in the proposal table/tree/custom If necessary in a tree, load the tree
- * children until the key is found
- * @param synchronous
- * true to execute the lookup call synchronous
- */
- void update(String searchText, boolean selectCurrentValue, boolean synchronous);
+ void update(IContentAssistSearchParam<LOOKUP_KEY> searchParam, boolean synchronous);
IContentAssistFieldDataFetchResult<LOOKUP_KEY> getResult();
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistSearchParam.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistSearchParam.java
new file mode 100644
index 0000000..3b8491a
--- /dev/null
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IContentAssistSearchParam.java
@@ -0,0 +1,13 @@
+package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
+
+public interface IContentAssistSearchParam<LOOKUP_KEY> {
+
+ String getSearchText();
+
+ LOOKUP_KEY getParentKey();
+
+ boolean isSelectCurrentValue();
+
+ boolean isByParentSearch();
+
+}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IncrementalTreeBuilder.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IncrementalTreeBuilder.java
index bed73e1..fb2ff12 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IncrementalTreeBuilder.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/IncrementalTreeBuilder.java
@@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -25,26 +26,76 @@
private final IKeyLookupProvider<LOOKUP_KEY> m_provider;
+ private final Map<LOOKUP_KEY, ILookupRow<LOOKUP_KEY>> m_keyCache = new HashMap<>();
+
public IncrementalTreeBuilder(IKeyLookupProvider<LOOKUP_KEY> provider) {
m_provider = provider;
}
- public Collection<ILookupRow<LOOKUP_KEY>> getRowsWithParents(List<? extends ILookupRow<LOOKUP_KEY>> lookupRows, ITree existingTree) {
- Map<LOOKUP_KEY, ILookupRow<LOOKUP_KEY>> allRows = new HashMap<>();
+ /**
+ * @param lookupRows
+ * @param selectedKey
+ * selected key or null
+ * @param existingTree
+ * @return
+ */
+ public List<ILookupRow<LOOKUP_KEY>> getRowsWithParents(List<ILookupRow<LOOKUP_KEY>> lookupRows, LOOKUP_KEY parent, ITree existingTree) {
+ List<ILookupRow<LOOKUP_KEY>> res = new ArrayList<>();
+
+ cacheKeys(lookupRows);
+ HashSet<LOOKUP_KEY> allRows = new HashSet<>();
+
List<List<ILookupRow<LOOKUP_KEY>>> paths = createPaths(lookupRows, existingTree);
- for (List<ILookupRow<LOOKUP_KEY>> path : paths) {
- for (ILookupRow<LOOKUP_KEY> row : path) {
- allRows.put(row.getKey(), row);
+ if (parent == null) {
+ //ensure, the path to the root is included for every node
+ for (List<ILookupRow<LOOKUP_KEY>> path : paths) {
+ for (ILookupRow<LOOKUP_KEY> row : path) {
+ if (!allRows.contains(row.getKey())) {
+ allRows.add(row.getKey());
+ res.add(row);
+ }
+
+ }
}
}
- return allRows.values();
+ else {
+ //ensure that rows other then children of the parent are included
+ for (List<ILookupRow<LOOKUP_KEY>> path : paths) {
+ if (contains(parent, path)) {
+ ILookupRow<LOOKUP_KEY> leaf = path.get(path.size() - 1);
+ if (!allRows.contains(leaf.getKey())) {
+ allRows.add(leaf.getKey());
+ res.add(leaf);
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ private boolean contains(LOOKUP_KEY key, List<ILookupRow<LOOKUP_KEY>> path) {
+ for (ILookupRow<LOOKUP_KEY> row : path) {
+ if (key.equals(row.getKey())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private ILookupRow<LOOKUP_KEY> getLookupRow(LOOKUP_KEY key) {
+ if (!m_keyCache.containsKey(key)) {
+ ILookupRow<LOOKUP_KEY> row = m_provider.getLookupRow(key);
+ m_keyCache.put(key, row);
+ return row;
+ }
+ return m_keyCache.get(key);
}
/**
* Collects the path to the root for each node using the existing tree to lookup parent nodes, if possible. Each path
* starts with the root node.
*/
- public List<List<ILookupRow<LOOKUP_KEY>>> createPaths(List<? extends ILookupRow<LOOKUP_KEY>> lookupRows, ITree existingTree) {
+ public List<List<ILookupRow<LOOKUP_KEY>>> createPaths(Collection<? extends ILookupRow<LOOKUP_KEY>> lookupRows, ITree existingTree) {
Map<LOOKUP_KEY, ILookupRow<LOOKUP_KEY>> parentMap = createParentMap(existingTree);
List<List<ILookupRow<LOOKUP_KEY>>> paths = new ArrayList<>();
for (ILookupRow<LOOKUP_KEY> row : lookupRows) {
@@ -54,7 +105,7 @@
while (r != null) {
path.add(0, r);
if (!parentMap.containsKey(r.getKey())) {
- ILookupRow<LOOKUP_KEY> parentRow = m_provider.getLookupRow(r.getParentKey());
+ ILookupRow<LOOKUP_KEY> parentRow = getLookupRow(r.getParentKey());
parentMap.put(r.getKey(), parentRow);
}
r = parentMap.get(r.getKey());
@@ -64,6 +115,12 @@
return paths;
}
+ private void cacheKeys(Collection<? extends ILookupRow<LOOKUP_KEY>> lookupRows) {
+ for (ILookupRow<LOOKUP_KEY> row : lookupRows) {
+ m_keyCache.put(row.getKey(), row);
+ }
+ }
+
/**
* Assumes values of type LOOKUP_ROW_TYPE in tree cells
*/
@@ -85,6 +142,7 @@
ILookupRow<LOOKUP_KEY> row = getLookupRow(node);
if (row != null) {
LOOKUP_KEY key = row.getKey();
+ m_keyCache.put(key, row);
map.put(key, getLookupRow(parent));
}
return true;
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/LookupJobHelper.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/LookupJobHelper.java
new file mode 100644
index 0000000..2547f43
--- /dev/null
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/LookupJobHelper.java
@@ -0,0 +1,50 @@
+package org.eclipse.scout.rt.client.ui.form.fields.smartfield;
+
+import org.eclipse.scout.rt.client.context.ClientRunContexts;
+import org.eclipse.scout.rt.platform.annotations.Internal;
+import org.eclipse.scout.rt.platform.job.DoneEvent;
+import org.eclipse.scout.rt.platform.job.IBlockingCondition;
+import org.eclipse.scout.rt.platform.job.IDoneHandler;
+import org.eclipse.scout.rt.platform.job.IFuture;
+import org.eclipse.scout.rt.platform.job.Jobs;
+
+/**
+ * Job helper methods to be included later in the job framework.
+ */
+@Internal
+public class LookupJobHelper {
+
+ /**
+ * await result while freeing model thread
+ */
+ public static <T> T await(IFuture<T> futureRes) {
+ final IBlockingCondition bc = Jobs.newBlockingCondition(true);
+ futureRes.whenDone(new IDoneHandler<T>() {
+
+ @Override
+ public void onDone(DoneEvent<T> event) {
+ bc.setBlocking(false);
+ }
+ }, ClientRunContexts.copyCurrent());
+ bc.waitFor();
+
+ return futureRes.awaitDoneAndGet();
+ }
+
+ /**
+ * await result while freeing model thread
+ */
+ public static <T> void awaitDone(IFuture<T> futureRes) {
+ final IBlockingCondition bc = Jobs.newBlockingCondition(true);
+ futureRes.whenDone(new IDoneHandler<T>() {
+
+ @Override
+ public void onDone(DoneEvent<T> event) {
+ bc.setBlocking(false);
+ }
+ }, ClientRunContexts.copyCurrent());
+ bc.waitFor();
+ futureRes.awaitDone();
+ }
+
+}
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TableProposalChooser.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TableProposalChooser.java
index 83cf86b..baeeb67 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TableProposalChooser.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TableProposalChooser.java
@@ -18,11 +18,7 @@
import org.eclipse.scout.rt.client.ui.basic.table.TableListener;
import org.eclipse.scout.rt.platform.Order;
import org.eclipse.scout.rt.platform.annotations.ConfigOperation;
-import org.eclipse.scout.rt.platform.exception.ProcessingException;
-import org.eclipse.scout.rt.platform.status.IStatus;
-import org.eclipse.scout.rt.platform.status.Status;
import org.eclipse.scout.rt.platform.util.CollectionUtility;
-import org.eclipse.scout.rt.shared.ScoutTexts;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -107,8 +103,8 @@
String searchText = null;
if (result != null) {
rows = result.getLookupRows();
- selectCurrentValue = result.isSelectCurrentValue();
- searchText = result.getSearchText();
+ selectCurrentValue = result.getSearchParam().isSelectCurrentValue();
+ searchText = result.getSearchParam().getSearchText();
}
if (rows == null) {
rows = CollectionUtility.emptyArrayList();
@@ -145,50 +141,6 @@
}
}
- protected void updateStatus(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
- List<? extends ILookupRow<LOOKUP_KEY>> rows = null;
- RuntimeException exception = null;
- String searchText = null;
- if (result != null) {
- rows = result.getLookupRows();
- exception = result.getException();
- searchText = result.getSearchText();
- }
- if (rows == null) {
- rows = CollectionUtility.emptyArrayList();
- }
- String statusText = null;
- int severity = IStatus.INFO;
- if (exception != null) {
- if (exception instanceof ProcessingException) {
- statusText = ((ProcessingException) exception).getStatus().getMessage();
- }
- else {
- statusText = exception.getMessage();
- }
- severity = IStatus.ERROR;
- }
- else if (rows.size() <= 0) {
- if (getContentAssistField().getWildcard().equals(searchText)) {
- statusText = ScoutTexts.get("SmartFieldNoDataFound");
- }
- else {
- statusText = ScoutTexts.get("SmartFieldCannotComplete", (searchText == null) ? ("") : (searchText));
- }
- severity = IStatus.WARNING;
- }
- else if (rows.size() > m_contentAssistField.getBrowseMaxRowCount()) {
- statusText = ScoutTexts.get("SmartFieldMoreThanXRows", "" + m_contentAssistField.getBrowseMaxRowCount());
- severity = IStatus.INFO;
- }
- if (statusText != null) {
- setStatus(new Status(statusText, severity));
- }
- else {
- setStatus(null);
- }
- }
-
/**
* Override this method to change that behavior of what is a single match.
* <p>
diff --git a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TreeProposalChooser.java b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TreeProposalChooser.java
index 3e6bd01..7487fd0 100644
--- a/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TreeProposalChooser.java
+++ b/org.eclipse.scout.rt.client/src/main/java/org/eclipse/scout/rt/client/ui/form/fields/smartfield/TreeProposalChooser.java
@@ -13,11 +13,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.regex.Pattern;
-import org.eclipse.scout.rt.client.context.ClientRunContexts;
-import org.eclipse.scout.rt.client.job.ModelJobs;
-import org.eclipse.scout.rt.client.session.ClientSessionProvider;
import org.eclipse.scout.rt.client.ui.MouseButton;
import org.eclipse.scout.rt.client.ui.basic.cell.Cell;
import org.eclipse.scout.rt.client.ui.basic.tree.AbstractTree;
@@ -25,27 +21,13 @@
import org.eclipse.scout.rt.client.ui.basic.tree.AbstractTreeNodeBuilder;
import org.eclipse.scout.rt.client.ui.basic.tree.ITree;
import org.eclipse.scout.rt.client.ui.basic.tree.ITreeNode;
-import org.eclipse.scout.rt.client.ui.basic.tree.ITreeNodeFilter;
import org.eclipse.scout.rt.client.ui.basic.tree.ITreeVisitor;
-import org.eclipse.scout.rt.client.ui.desktop.IDesktop;
-import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.Order;
import org.eclipse.scout.rt.platform.annotations.ConfigOperation;
-import org.eclipse.scout.rt.platform.exception.ExceptionHandler;
-import org.eclipse.scout.rt.platform.job.DoneEvent;
-import org.eclipse.scout.rt.platform.job.IDoneHandler;
-import org.eclipse.scout.rt.platform.job.IFuture;
-import org.eclipse.scout.rt.platform.status.IStatus;
-import org.eclipse.scout.rt.platform.status.Status;
+import org.eclipse.scout.rt.platform.holders.Holder;
import org.eclipse.scout.rt.platform.util.CollectionUtility;
import org.eclipse.scout.rt.platform.util.CompareUtility;
-import org.eclipse.scout.rt.platform.util.StringUtility;
-import org.eclipse.scout.rt.platform.util.TriState;
-import org.eclipse.scout.rt.platform.util.concurrent.IRunnable;
-import org.eclipse.scout.rt.shared.ScoutTexts;
-import org.eclipse.scout.rt.shared.TEXTS;
import org.eclipse.scout.rt.shared.services.lookup.ILookupRow;
-import org.eclipse.scout.rt.shared.services.lookup.ILookupRowFetchedCallback;
import org.eclipse.scout.rt.shared.services.lookup.LookupRow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -61,23 +43,15 @@
*/
public class TreeProposalChooser<LOOKUP_KEY> extends AbstractProposalChooser<ITree, LOOKUP_KEY> {
private static final Logger LOG = LoggerFactory.getLogger(TreeProposalChooser.class);
-
- private P_ActiveNodesFilter m_activeNodesFilter;
- private P_MatchingNodesFilter m_matchingNodesFilter;
- private boolean m_selectCurrentValueRequested;
- private boolean m_populateInitialTreeDone;
- private volatile IFuture<Void> m_initialPolulatorFuture;
- private boolean m_modelExternallyManaged = false;
+ private final KeyLookupProvider m_keyLookupProvider;
public TreeProposalChooser(IContentAssistField<?, LOOKUP_KEY> contentAssistField, boolean allowCustomText) {
super(contentAssistField, allowCustomText);
+ m_keyLookupProvider = new KeyLookupProvider();
}
@Override
protected ITree createModel() {
- m_activeNodesFilter = new P_ActiveNodesFilter();
- m_matchingNodesFilter = new P_MatchingNodesFilter();
-
ITree tree = createConfiguredOrDefaultModel(ITree.class);
tree.setDefaultIconId(m_contentAssistField.getBrowseIconId());
return tree;
@@ -110,10 +84,6 @@
@Override
public void dispose() {
- if (m_initialPolulatorFuture != null) {
- m_initialPolulatorFuture.cancel(false);
- }
-
m_model.disposeTree();
m_model = null;
}
@@ -125,220 +95,151 @@
@Override
protected void dataFetchedDelegateImpl(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result, int maxCount) {
- String searchText = null;
- boolean selectCurrentValue = false;
- if (result != null) {
- selectCurrentValue = result.isSelectCurrentValue();
- searchText = result.getSearchText();
+ if (result.getException() == null) {
+ List<ITreeNode> subTree = getSubtree(result);
+ ITreeNode parentNode = getParent(result);
+
+ try {
+ if (m_model != null) {
+ m_model.setTreeChanging(true);
+ updateSubTree(m_model, parentNode, subTree);
+ expand();
+ selectNode(result);
+ }
+
+ }
+ finally {
+ if (m_model != null) {
+ m_model.setTreeChanging(false);
+ }
+ }
}
- if (!m_populateInitialTreeDone) {
- m_selectCurrentValueRequested = selectCurrentValue;
- return;
- }
- try {
- m_model.setTreeChanging(true);
- //
- m_matchingNodesFilter.update(searchText);
- m_model.addNodeFilter(m_matchingNodesFilter);
- }
- finally {
- m_model.setTreeChanging(false);
- }
- setStatus(null);
+ updateStatus(result);
}
/**
- * Populate initial tree using a {@link ClientAsyncJob}. Amount of tree loaded is depending on
- * {@link IContentAssistField#isBrowseLoadIncremental()}.
- * <p>
- * loadIncremnental only loads the roots, whereas !loadIncremental loads the complete tree. Normally the latter is
- * configured together with {@link IContentAssistField#isBrowseAutoExpandAll()}
+ * Selects a node depending on the search, if available. Selected node must be loaded.
*/
- @Override
- protected void init() {
- if (m_contentAssistField.isBrowseLoadIncremental()) {
- // Load lookup rows asynchronously
- loadRootNode();
+ private void selectNode(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
+ if (result.getSearchParam().isSelectCurrentValue()) {
+ ITreeNode currentValueNode = getNode(m_contentAssistField.getValueAsLookupKey());
+ if (currentValueNode != null) {
+ selectValue(currentValueNode);
+ }
}
else {
- setStatus(new Status(ScoutTexts.get("searchingProposals"), IStatus.OK));
- // Load lookup rows asynchronously
- m_initialPolulatorFuture = m_contentAssistField.callBrowseLookupInBackground(m_contentAssistField.getWildcard(), 100000, TriState.UNDEFINED, new ILookupRowFetchedCallback<LOOKUP_KEY>() {
-
- @Override
- public void onSuccess(List<? extends ILookupRow<LOOKUP_KEY>> rows) {
- m_initialPolulatorFuture = null;
-
- List<ITreeNode> subTree = new P_TreeNodeBuilder().createTreeNodes(rows, ITreeNode.STATUS_NON_CHANGED, true);
-
- m_model.setTreeChanging(true);
- try {
- updateSubTree(m_model, m_model.getRootNode(), subTree);
- if (m_contentAssistField.isBrowseAutoExpandAll()) {
- m_model.expandAll(m_model.getRootNode());
- }
- commitPopulateInitialTree();
- setStatus(null);
- }
- catch (RuntimeException e) {
- setStatus(new Status(TEXTS.get("RequestProblem"), IStatus.ERROR));
- }
- finally {
- m_model.setTreeChanging(false);
- }
- }
-
- @Override
- public void onFailure(RuntimeException e) {
- m_initialPolulatorFuture = null;
-
- setStatus(new Status(TEXTS.get("RequestProblem"), IStatus.ERROR));
- }
- });
- m_initialPolulatorFuture.addExecutionHint(IContentAssistField.EXECUTION_HINT_INITIAL_LOOKUP);
+ //select first search text
+ selectNodeByText(result.getSearchParam().getSearchText());
}
}
- /**
- * Called when the initial tree has been loaded and the form is therefore ready to accept
- * {@link #update(boolean, boolean)} requests.
- */
- private void commitPopulateInitialTree() {
- updateActiveFilter();
- if (m_selectCurrentValueRequested) {
- if (m_model.getSelectedNodeCount() == 0) {
- selectCurrentValueInternal();
- }
+ private void expand() {
+ if (m_contentAssistField.isBrowseAutoExpandAll() && !m_contentAssistField.isBrowseLoadIncremental()) {
+ m_model.expandAll(m_model.getRootNode());
}
- m_populateInitialTreeDone = true;
- m_contentAssistField.doSearch(m_selectCurrentValueRequested, true);
- }
-
- private boolean selectCurrentValueInternal() {
- final LOOKUP_KEY selectedKey = m_contentAssistField.getValueAsLookupKey();
- if (selectedKey != null) {
- //check existing tree
- final ArrayList<ITreeNode> matchingNodes = new ArrayList<ITreeNode>();
- m_model.visitTree(new ITreeVisitor() {
- @Override
- public boolean visit(ITreeNode node) {
- Object val = node.getCell().getValue();
- if (val instanceof ILookupRow && CompareUtility.equals(selectedKey, ((ILookupRow) val).getKey())) {
- matchingNodes.add(node);
- }
- return true;
- }
- });
- if (matchingNodes.size() > 0) {
- selectValue(m_model, matchingNodes.get(0));
- // ticket 87030
- for (int i = 1; i < matchingNodes.size(); i++) {
- ITreeNode node = matchingNodes.get(i);
- m_model.setNodeExpanded(node, true);
- m_model.ensureVisible(matchingNodes.get(i));
- }
- return true;
- }
- else {
- // load tree
- ITreeNode node = loadNodeWithKey(selectedKey);
- if (node != null) {
- selectValue(m_model, node);
- return true;
- }
- }
- }
- return false;
- }
-
- private void selectValue(ITree tree, ITreeNode node) {
- if (tree == null || node == null) {
- return;
- }
- tree.selectNode(node);
- if (tree.isCheckable()) {
- tree.setNodeChecked(node, true);
+ else {
+ expandNodesWithChildren();
}
}
- private ITreeNode loadNodeWithKey(LOOKUP_KEY key) {
- ArrayList<ILookupRow<LOOKUP_KEY>> path = new ArrayList<ILookupRow<LOOKUP_KEY>>();
- LOOKUP_KEY t = key;
- while (t != null) {
- ILookupRow<LOOKUP_KEY> row = getLookupRowFor(t);
- if (row != null) {
- path.add(0, row);
- t = row.getParentKey();
+ private ITreeNode getParent(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
+ if (m_model != null) {
+ if (result.getSearchParam().getParentKey() == null) {
+ return m_model.getRootNode();
}
- else {
- t = null;
- }
- }
- ITreeNode parentNode = m_model.getRootNode();
- for (int i = 0; i < path.size() && parentNode != null; i++) {
- parentNode.ensureChildrenLoaded();
- parentNode.setExpanded(true);
- Object childKey = path.get(i).getKey();
- ITreeNode nextNode = null;
- for (ITreeNode n : parentNode.getChildNodes()) {
- if (n.getCell().getValue() instanceof ILookupRow) {
- if (CompareUtility.equals(((ILookupRow) n.getCell().getValue()).getKey(), childKey)) {
- nextNode = n;
- break;
- }
- }
- }
- parentNode = nextNode;
- }
- return parentNode;
- }
-
- private ILookupRow<LOOKUP_KEY> getLookupRowFor(LOOKUP_KEY key) {
- if (key instanceof Number && ((Number) key).longValue() == 0) {
- key = null;
- }
- if (key != null) {
- IContentAssistField<?, LOOKUP_KEY> sf = (IContentAssistField<?, LOOKUP_KEY>) m_contentAssistField;
- for (ILookupRow<LOOKUP_KEY> row : sf.callKeyLookup(key)) {
- return row;
- }
+ return getNode(result.getSearchParam().getParentKey());
}
return null;
}
- private void updateActiveFilter() {
- try {
- m_model.setTreeChanging(true);
- //
- if (m_contentAssistField.isActiveFilterEnabled()) {
- m_activeNodesFilter.update(m_contentAssistField.getActiveFilter());
- }
- else {
- m_activeNodesFilter.update(TriState.TRUE);
- }
- m_model.addNodeFilter(m_activeNodesFilter);
+ private List<ITreeNode> getSubtree(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
+ List<? extends ILookupRow<LOOKUP_KEY>> rows = getRows(result);
+ boolean markChildrenLoaded = !m_contentAssistField.isBrowseLoadIncremental() || !result.getSearchParam().isByParentSearch();
+ return new P_TreeNodeBuilder()
+ .createTreeNodes(rows, ITreeNode.STATUS_NON_CHANGED, markChildrenLoaded);
+ }
+
+ /**
+ * @return all new rows to be inserted (including parents of search result)
+ */
+ private List<? extends ILookupRow<LOOKUP_KEY>> getRows(IContentAssistFieldDataFetchResult<LOOKUP_KEY> result) {
+ LOOKUP_KEY parent = result.getSearchParam().getParentKey();
+ return new IncrementalTreeBuilder<LOOKUP_KEY>(m_keyLookupProvider)
+ .getRowsWithParents(result.getLookupRows(), parent, m_model);
+ }
+
+ /**
+ * find first node with matching text
+ */
+ private void selectNodeByText(final String searchText) {
+ if (searchText != null) {
+ m_model.visitTree(new ITreeVisitor() {
+
+ @Override
+ public boolean visit(ITreeNode node) {
+ if (searchText.equals(node.getCell().getText())) {
+ selectValue(node);
+ return false;
+ }
+ return true;
+ }
+ });
}
- finally {
- m_model.setTreeChanging(false);
+ }
+
+ private void expandNodesWithChildren() {
+ m_model.visitTree(new ITreeVisitor() {
+
+ @Override
+ public boolean visit(ITreeNode node) {
+ if (node.getChildNodeCount() > 0) {
+ node.setChildrenLoaded(true);
+ node.setExpanded(true);
+ }
+ return true;
+ }
+ });
+ }
+
+ private void selectValue(ITreeNode node) {
+ if (node != null) {
+ m_model.selectNode(node);
+ if (m_model.isCheckable()) {
+ m_model.setNodeChecked(node, true);
+ }
}
}
+ /**
+ * Node with a given key. Assumes the node is already loaded at this point
+ */
+ private ITreeNode getNode(final LOOKUP_KEY key) {
+ final Holder<ITreeNode> holder = new Holder<ITreeNode>(ITreeNode.class);
+ m_model.visitTree(new ITreeVisitor() {
+
+ @Override
+ public boolean visit(ITreeNode node) {
+ if (node.getCell().getValue() instanceof ILookupRow && CompareUtility.equals(((ILookupRow) node.getCell().getValue()).getKey(), key)) {
+ holder.setValue(node);
+ return false;
+ }
+ return true;
+ }
+ });
+ return holder.getValue();
+ }
+
private void updateSubTree(ITree tree, final ITreeNode parentNode, List<ITreeNode> subTree) {
if (tree == null || parentNode == null || subTree == null) {
return;
}
tree.removeAllChildNodes(parentNode);
tree.addChildNodes(parentNode, subTree);
- }
-
- public void loadRootNode() {
- if (m_model != null && !m_modelExternallyManaged) {
- loadChildNodes(m_model.getRootNode());
- }
+ parentNode.setChildrenLoaded(true);
}
public void loadChildNodes(ITreeNode parentNode) {
- if (m_model != null && !m_modelExternallyManaged) {
+ if (m_model != null) {
try {
m_model.setTreeChanging(true);
//
@@ -359,100 +260,13 @@
@SuppressWarnings("unchecked")
protected void execLoadChildNodes(final ITreeNode parentNode) {
if (m_contentAssistField.isBrowseLoadIncremental()) {
- final IStatus originalStatus = getStatus();
- setStatus(new Status(ScoutTexts.get("searchingProposals"), IStatus.OK));
-
- //load node
ILookupRow<LOOKUP_KEY> b = (LookupRow) (parentNode != null ? parentNode.getCell().getValue() : null);
LOOKUP_KEY parentKey = b != null ? b.getKey() : null;
- m_contentAssistField.callSubTreeLookupInBackground(parentKey, TriState.UNDEFINED, false)
- .whenDone(new IDoneHandler<List<ILookupRow<LOOKUP_KEY>>>() {
- @Override
- public void onDone(DoneEvent<List<ILookupRow<LOOKUP_KEY>>> event) {
- final List<? extends ILookupRow<LOOKUP_KEY>> result = event.getResult();
- final Throwable exception = event.getException();
-
- ModelJobs.schedule(new IRunnable() {
-
- @Override
- public void run() {
- if (result != null) {
- List<ITreeNode> subTree = new P_TreeNodeBuilder().createTreeNodes(result, ITreeNode.STATUS_NON_CHANGED, false);
- updateSubTree(m_model, parentNode, subTree);
- commitPopulateInitialTree();
-
- // hide loading status
- setStatus(originalStatus);
- }
- else if (exception != null) {
- LOG.error("Error in subtree lookup", exception);
- setStatus(new Status(TEXTS.get("RequestProblem"), IStatus.ERROR));
- }
- }
- }, ModelJobs.newInput(ClientRunContexts.copyCurrent()));
- }
- }, ClientRunContexts.copyCurrent());
- }
-
- }
-
- /**
- * @return the pattern used to filter tree nodes based on the text typed into the smartfield
- */
- @ConfigOperation
- @Order(20)
- protected Pattern execCreatePatternForTreeFilter(String filterText) {
- // check pattern
- String s = filterText;
- if (s == null) {
- s = "";
- }
- s = s.toLowerCase();
- IDesktop desktop = ClientSessionProvider.currentSession().getDesktop();
- if (desktop != null && desktop.isAutoPrefixWildcardForTextSearch()) {
- s = getContentAssistField().getWildcard() + s;
- }
- s = s.replace(getContentAssistField().getWildcard(), "@wildcard@");
- s = StringUtility.escapeRegexMetachars(s);
- s = s.replace("@wildcard@", ".*");
- if (!s.endsWith(".*")) {
- s = s + ".*";
- }
- return Pattern.compile(s, Pattern.DOTALL);
-
- }
-
- /**
- * @return true if the node is accepted by the tree filter pattern defined in
- * {@link #execCreatePatternForTreeFilter(String)}
- */
- @ConfigOperation
- @Order(30)
- protected boolean execAcceptNodeByTreeFilter(Pattern filterPattern, ITreeNode node, int level) {
- IContentAssistField<?, LOOKUP_KEY> sf = m_contentAssistField;
- @SuppressWarnings("unchecked")
- ILookupRow<LOOKUP_KEY> row = (ILookupRow<LOOKUP_KEY>) node.getCell().getValue();
- if (node.isChildrenLoaded()) {
- if (row != null) {
- String q1 = node.getTree().getPathText(node, "\n");
- String q2 = node.getTree().getPathText(node, " ");
- if (q1 != null && q2 != null) {
- String[] path = (q1 + "\n" + q2).split("\n");
- for (String pathText : path) {
- if (pathText != null && filterPattern.matcher(pathText.toLowerCase()).matches()) {
- // use "level-1" because a tree smart field assumes its tree to
- // have multiple roots, but the ITree model is built as
- // single-root tree with invisible root node
- if (sf.acceptBrowseHierarchySelection(row.getKey(), level - 1, node.isLeaf())) {
- return true;
- }
- }
- }
- }
- return false;
+ getContentAssistField().doSearch(ContentAssistSearchParam.createParentParam(parentKey, false), false);
+ if (parentNode != null) {
+ parentNode.setChildrenLoaded(true);
}
}
- return true;
}
/**
@@ -463,45 +277,26 @@
@Order(40)
@Override
protected ILookupRow<LOOKUP_KEY> execGetSingleMatch() {
- // when load incremental is set, don't visit the tree but use text-to-key
- // lookup method on smartfield.
- if (m_contentAssistField.isBrowseLoadIncremental()) {
- try {
- List<? extends ILookupRow<LOOKUP_KEY>> rows = m_contentAssistField.callTextLookup(getSearchText(), 2);
- if (rows != null && rows.size() == 1) {
- return rows.get(0);
+ final List<ILookupRow<LOOKUP_KEY>> foundLeafs = new ArrayList<>();
+ ITreeVisitor v = new ITreeVisitor() {
+ @Override
+ public boolean visit(ITreeNode node) {
+ if (node.isEnabled() && node.isLeaf()) {
+ @SuppressWarnings("unchecked")
+ ILookupRow<LOOKUP_KEY> row = (ILookupRow<LOOKUP_KEY>) node.getCell().getValue();
+ if (row != null && row.isEnabled()) {
+ foundLeafs.add(row);
+ }
}
- else {
- return null;
- }
+ return foundLeafs.size() <= 2;
}
- catch (RuntimeException e) {
- BEANS.get(ExceptionHandler.class).handle(e);
- return null;
- }
+ };
+ m_model.visitVisibleTree(v);
+ if (foundLeafs.size() == 1) {
+ return foundLeafs.get(0);
}
else {
- final List<ILookupRow<LOOKUP_KEY>> foundLeafs = new ArrayList<ILookupRow<LOOKUP_KEY>>();
- ITreeVisitor v = new ITreeVisitor() {
- @Override
- public boolean visit(ITreeNode node) {
- if (node.isEnabled() && node.isLeaf()) {
- @SuppressWarnings("unchecked")
- ILookupRow<LOOKUP_KEY> row = (ILookupRow<LOOKUP_KEY>) node.getCell().getValue();
- if (row != null && row.isEnabled()) {
- foundLeafs.add(row);
- }
- }
- return foundLeafs.size() <= 2;
- }
- };
- m_model.visitVisibleTree(v);
- if (foundLeafs.size() == 1) {
- return foundLeafs.get(0);
- }
- else {
- return null;
- }
+ return null;
}
}
@@ -551,46 +346,23 @@
}
}
- private class P_MatchingNodesFilter implements ITreeNodeFilter {
- private Pattern m_searchPattern;
-
- public P_MatchingNodesFilter() {
- }
-
- public void update(String text) {
- m_searchPattern = execCreatePatternForTreeFilter(text);
- }
+ private class KeyLookupProvider implements IKeyLookupProvider<LOOKUP_KEY> {
@Override
- public boolean accept(ITreeNode node, int level) {
- return execAcceptNodeByTreeFilter(m_searchPattern, node, level);
+ public ILookupRow<LOOKUP_KEY> getLookupRow(LOOKUP_KEY key) {
+ //do not cancel lookups that are already in progress
+ List<ILookupRow<LOOKUP_KEY>> rows = LookupJobHelper.await(m_contentAssistField.callKeyLookupInBackground(key, false));
+ if (rows.size() == 0) {
+ return null;
+ }
+ else if (rows.size() > 1) {
+ LOG.error("More than one row found for key {}", key);
+ return null;
+ }
+
+ return rows.get(0);
}
+
}
- private class P_ActiveNodesFilter implements ITreeNodeFilter {
- private TriState m_ts;
-
- public P_ActiveNodesFilter() {
- }
-
- public void update(TriState ts) {
- m_ts = ts;
- }
-
- @Override
- public boolean accept(ITreeNode node, int level) {
- if (m_ts.isUndefined()) {
- return true;
- }
- else {
- ILookupRow row = (LookupRow) node.getCell().getValue();
- if (row != null) {
- return row.isActive() == m_ts.equals(TriState.TRUE);
- }
- else {
- return true;
- }
- }
- }
- }
}
diff --git a/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/job/WhenDoneScheduleTest.java b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/job/WhenDoneScheduleTest.java
new file mode 100644
index 0000000..0f8350f
--- /dev/null
+++ b/org.eclipse.scout.rt.platform.test/src/test/java/org/eclipse/scout/rt/platform/job/WhenDoneScheduleTest.java
@@ -0,0 +1,616 @@
+package org.eclipse.scout.rt.platform.job;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.scout.rt.platform.BEANS;
+import org.eclipse.scout.rt.platform.context.RunContexts;
+import org.eclipse.scout.rt.platform.context.RunMonitor;
+import org.eclipse.scout.rt.platform.exception.DefaultExceptionTranslator;
+import org.eclipse.scout.rt.platform.util.concurrent.FutureCancelledException;
+import org.eclipse.scout.rt.platform.util.concurrent.IBiFunction;
+import org.eclipse.scout.rt.platform.util.concurrent.IRunnable;
+import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedException;
+import org.eclipse.scout.rt.platform.util.concurrent.TimedOutException;
+import org.eclipse.scout.rt.testing.platform.job.JobTestUtil;
+import org.eclipse.scout.rt.testing.platform.runner.PlatformTestRunner;
+import org.eclipse.scout.rt.testing.platform.runner.Times;
+import org.eclipse.scout.rt.testing.platform.util.BlockingCountDownLatch;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(PlatformTestRunner.class)
+@Times(100) // for regression purpose
+public class WhenDoneScheduleTest {
+
+ private static final String JOB_MARKER = UUID.randomUUID().toString();
+
+ @Test
+ public void testCascading() {
+ IFuture<String> future = Jobs.schedule(
+ new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ return "a";
+ }
+ }, Jobs.newInput())
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable u) {
+ return result + "b";
+ }
+ }, Jobs.newInput())
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable u) {
+ return result + "c";
+ }
+ }, Jobs.newInput())
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable u) {
+ return result + "d";
+ }
+ }, Jobs.newInput());
+
+ assertEquals("abcd", future.awaitDoneAndGet());
+ }
+
+ @Test
+ public void testCascadingException() {
+ final Exception exception1 = new Exception("JUnit test exception 1");
+ final RuntimeException exception2 = new RuntimeException("JUnit test exception 2");
+ final RuntimeException exception3 = new RuntimeException("JUnit test exception 3");
+ final RuntimeException exception4 = new RuntimeException("JUnit test exception 4");
+
+ IFuture<String> future = Jobs.schedule(
+ new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ throw exception1;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false))
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable e) {
+ assertSame(exception1, e);
+ throw exception2;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false))
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable e) {
+ assertSame(exception2, e);
+ throw exception3;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false))
+ .whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable e) {
+ assertSame(exception3, e);
+ throw exception4;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false));
+
+ try {
+ future.awaitDoneAndGet();
+ fail("exception expected");
+ }
+ catch (Exception e) {
+ assertSame(exception4, e);
+ }
+ }
+
+ @Test
+ public void testNotSameFuture() {
+ // Schedule future
+ IFuture<Void> future = Jobs.schedule(new IRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ }
+ }, Jobs.newInput());
+
+ // Schedule function
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<Void, Throwable, Void>() {
+
+ @Override
+ public Void apply(Void t, Throwable u) {
+ return null;
+ }
+ }, Jobs.newInput());
+
+ assertNotSame(future, functionFuture);
+ }
+
+ @Test
+ public void testResult() {
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ return "abc";
+ }
+ }, Jobs.newInput());
+
+ // Schedule function
+ IFuture<Integer> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Integer>() {
+
+ @Override
+ public Integer apply(String result, Throwable error) {
+ return result.length();
+ }
+ }, Jobs.newInput());
+
+ assertEquals("abc", future.awaitDoneAndGet());
+ assertEquals(3, functionFuture.awaitDoneAndGet().intValue());
+ }
+
+ @Test
+ public void testCancellationOfFuture() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ setupLatch.countDownAndBlock();
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true) // to not work with JUnitExceptionHandler
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ future.cancel(true);
+
+ // Wait until the job jobs are done
+ Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder()
+ .andMatchExecutionHint(JOB_MARKER)
+ .toFilter(), 5, TimeUnit.SECONDS);
+
+ // Verify that the function is not executed
+ assertFalse(functionExecuted.get());
+
+ // Verify that both futures are cancelled
+ assertFutureCancelled(future);
+ assertFutureCancelled(functionFuture);
+ }
+
+ @Test
+ public void testCancellationOfFunctionFuture() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ setupLatch.countDownAndBlock();
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true) // to not work with JUnitExceptionHandler
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ functionFuture.cancel(true);
+ setupLatch.unblock();
+
+ // Wait until the job jobs are done
+ Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder()
+ .andMatchExecutionHint(JOB_MARKER)
+ .toFilter(), 5, TimeUnit.SECONDS);
+
+ // Verify that the function is not executed
+ assertFalse(functionExecuted.get());
+
+ // Verify that future is not cancelled
+ assertEquals("abc", future.awaitDoneAndGet());
+ assertFalse(future.isCancelled());
+ assertTrue(future.isDone());
+
+ // Verify that function future is cancelled
+ assertFutureCancelled(functionFuture);
+ }
+
+ @Test
+ public void testCancellationOfFutureRunMonitor() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ RunMonitor runMonitor = BEANS.get(RunMonitor.class);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ setupLatch.countDownAndBlock();
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true) // to not work with JUnitExceptionHandler
+ .withRunContext(RunContexts.empty().withRunMonitor(runMonitor))
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ runMonitor.cancel(true);
+
+ // Wait until the job jobs are done
+ Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder()
+ .andMatchExecutionHint(JOB_MARKER)
+ .toFilter(), 5, TimeUnit.SECONDS);
+
+ // Verify that the function is not executed
+ assertFalse(functionExecuted.get());
+
+ // Verify that both futures are cancelled
+ assertFutureCancelled(future);
+ assertFutureCancelled(functionFuture);
+ }
+
+ @Test
+ public void testCancellationOfFunctionRunMonitor() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ setupLatch.countDownAndBlock();
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true) // to not work with JUnitExceptionHandler
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+
+ RunMonitor functionRunMonitor = BEANS.get(RunMonitor.class);
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withRunContext(RunContexts.empty().withRunMonitor(functionRunMonitor))
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ functionRunMonitor.cancel(true);
+ setupLatch.unblock();
+
+ // Wait until the job jobs are done
+ Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder()
+ .andMatchExecutionHint(JOB_MARKER)
+ .toFilter(), 5, TimeUnit.SECONDS);
+
+ // Verify that the function is not executed
+ assertFalse(functionExecuted.get());
+
+ // Verify that future is not cancelled
+ assertEquals("abc", future.awaitDoneAndGet());
+ assertFalse(future.isCancelled());
+ assertTrue(future.isDone());
+
+ // Verify that function future is cancelled
+ assertFutureCancelled(functionFuture);
+ }
+
+ @Test
+ public void testCancellationOfSharedRunMonitor() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ RunMonitor sharedRunMonitor = BEANS.get(RunMonitor.class);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ setupLatch.countDownAndBlock();
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true) // to not work with JUnitExceptionHandler
+ .withRunContext(RunContexts.empty().withRunMonitor(sharedRunMonitor))
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withRunContext(RunContexts.empty().withRunMonitor(sharedRunMonitor))
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ sharedRunMonitor.cancel(true);
+
+ // Wait until the job jobs are done
+ Jobs.getJobManager().awaitDone(Jobs.newFutureFilterBuilder()
+ .andMatchExecutionHint(JOB_MARKER)
+ .toFilter(), 5, TimeUnit.SECONDS);
+
+ // Verify that the function is not executed
+ assertFalse(functionExecuted.get());
+
+ // Verify that both futures are cancelled
+ assertFutureCancelled(future);
+ assertFutureCancelled(functionFuture);
+ }
+
+ @Test
+ public void testPostCancellation() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExecutionHint(JOB_MARKER));
+
+ // Schedule function
+ IFuture<String> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, String>() {
+
+ @Override
+ public String apply(String result, Throwable error) {
+ try {
+ setupLatch.countDownAndBlock();
+ }
+ catch (InterruptedException e) {
+ throw new ThreadInterruptedException("", e);
+ }
+ return result.toUpperCase();
+ }
+ }, Jobs.newInput()
+ .withExecutionHint(JOB_MARKER));
+
+ assertTrue(setupLatch.await());
+ // Cancel future which already completed
+ future.cancel(true);
+ setupLatch.unblock();
+ assertEquals("abc", future.awaitDoneAndGet());
+ assertEquals("ABC", functionFuture.awaitDoneAndGet());
+ assertFalse(future.isCancelled());
+ assertFalse(functionFuture.isCancelled());
+ }
+
+ @Test
+ @Times(20)
+ public void testSemaphore() throws InterruptedException {
+ final BlockingCountDownLatch setupLatch = new BlockingCountDownLatch(1);
+
+ IExecutionSemaphore mutex = Jobs.newExecutionSemaphore(1).seal();
+
+ // Schedule arbitrary job with same semaphore
+ Jobs.schedule(new IRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ setupLatch.countDownAndBlock();
+ }
+ }, Jobs.newInput()
+ .withExecutionSemaphore(mutex));
+
+ // Schedule future
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ return "abc";
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, true)); // to not work with JUnitExceptionHandler
+
+ // Schedule function with same semaphore
+ final AtomicBoolean functionExecuted = new AtomicBoolean(false);
+ IFuture<Void> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Void>() {
+
+ @Override
+ public Void apply(String result, Throwable error) {
+ functionExecuted.set(true);
+ return null;
+ }
+ }, Jobs.newInput()
+ .withExecutionSemaphore(mutex));
+
+ assertEquals("abc", future.awaitDoneAndGet(5, TimeUnit.SECONDS));
+
+ JobTestUtil.waitForPermitCompetitors(mutex, 2);
+
+ // Function future must not have commenced execution yet.
+ try {
+ functionFuture.awaitDone(100, TimeUnit.MILLISECONDS);
+ fail("timeout expected");
+ }
+ catch (TimedOutException e) {
+ // NOOP
+ }
+
+ assertFalse(functionExecuted.get());
+ setupLatch.unblock();
+ functionFuture.awaitDone(5, TimeUnit.SECONDS);
+ assertTrue(functionExecuted.get());
+
+ assertFalse(future.isCancelled());
+ assertTrue(future.isDone());
+ assertFalse(functionFuture.isCancelled());
+ assertTrue(functionFuture.isDone());
+ }
+
+ @Test
+ public void testException() throws Exception {
+ final Exception testException = new Exception("JUnit test exception");
+
+ final AtomicReference<Throwable> actualException = new AtomicReference<>();
+ final AtomicReference<Object> actualResult = new AtomicReference<>();
+
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ throw testException;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false)); // to not work with JUnitExceptionHandler
+
+ IFuture<Integer> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Integer>() {
+
+ @Override
+ public Integer apply(String result, Throwable error) {
+ actualResult.set(result);
+ actualException.set(error);
+ return 123;
+ }
+ }, Jobs.newInput());
+
+ // Wait until function future completed
+ functionFuture.awaitDone();
+
+ try {
+ future.awaitDoneAndGet(DefaultExceptionTranslator.class);
+ fail("exception expected");
+ }
+ catch (Exception e) {
+ assertSame(testException, e);
+ }
+
+ assertEquals(123, functionFuture.awaitDoneAndGet(DefaultExceptionTranslator.class).intValue());
+ assertSame(testException, actualException.get());
+ assertNull(actualResult.get());
+ assertFalse(future.isCancelled());
+ assertFalse(functionFuture.isCancelled());
+ }
+
+ @Test
+ public void testExceptionInFunction() throws Exception {
+ final RuntimeException testException = new RuntimeException("JUnit test exception");
+
+ final AtomicReference<Throwable> actualException = new AtomicReference<>();
+ final AtomicReference<Object> actualResult = new AtomicReference<>();
+
+ IFuture<String> future = Jobs.schedule(new Callable<String>() {
+
+ @Override
+ public String call() throws Exception {
+ return "abc";
+ }
+ }, Jobs.newInput());
+
+ IFuture<Integer> functionFuture = future.whenDoneSchedule(new IBiFunction<String, Throwable, Integer>() {
+
+ @Override
+ public Integer apply(String result, Throwable error) {
+ actualResult.set(result);
+ actualException.set(error);
+ throw testException;
+ }
+ }, Jobs.newInput()
+ .withExceptionHandling(null, false)); // to not work with JUnitExceptionHandler
+
+ // Wait until function future completed
+ functionFuture.awaitDone();
+
+ assertEquals("abc", future.awaitDoneAndGet());
+ assertNull(actualException.get());
+ assertEquals("abc", actualResult.get());
+
+ try {
+ functionFuture.awaitDoneAndGet();
+ fail("exception expected");
+ }
+ catch (Exception e) {
+ assertSame(testException, e);
+ }
+
+ assertFalse(future.isCancelled());
+ assertFalse(functionFuture.isCancelled());
+ }
+
+ private static void assertFutureCancelled(IFuture<?> future) {
+ try {
+ future.awaitDoneAndGet();
+ fail("cancellation excepted");
+ }
+ catch (FutureCancelledException e) {
+ assertTrue(future.isCancelled());
+ assertTrue(future.isDone());
+ }
+ }
+}
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/IFuture.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/IFuture.java
index b94789a..d62dbaf 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/IFuture.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/IFuture.java
@@ -14,6 +14,7 @@
import java.util.concurrent.TimeUnit;
import org.eclipse.scout.rt.platform.context.RunContext;
+import org.eclipse.scout.rt.platform.context.RunMonitor;
import org.eclipse.scout.rt.platform.exception.DefaultRuntimeExceptionTranslator;
import org.eclipse.scout.rt.platform.exception.IExceptionTranslator;
import org.eclipse.scout.rt.platform.filter.AndFilter;
@@ -25,6 +26,7 @@
import org.eclipse.scout.rt.platform.job.listener.JobEvent;
import org.eclipse.scout.rt.platform.util.IRegistrationHandle;
import org.eclipse.scout.rt.platform.util.concurrent.FutureCancelledException;
+import org.eclipse.scout.rt.platform.util.concurrent.IBiFunction;
import org.eclipse.scout.rt.platform.util.concurrent.ICancellable;
import org.eclipse.scout.rt.platform.util.concurrent.ThreadInterruptedException;
import org.eclipse.scout.rt.platform.util.concurrent.TimedOutException;
@@ -260,6 +262,32 @@
IFuture<RESULT> whenDone(IDoneHandler<RESULT> callback, RunContext runContext);
/**
+ * Schedules a new job to execute the given function after <code>this</code> future completes. Thereby, the function
+ * is provided with the result or failure of <code>this</code> future, and is invoked only if neither
+ * <code>this</code> future, nor the function's future, nor the {@link RunMonitor} associated with the function's job
+ * input is cancelled. If the evaluation of the function throws an exception, it is relayed to consumers of the
+ * returned future.
+ * <p>
+ * Unlike {@link #whenDone(IDoneHandler, RunContext)}, the future returned does not represent <code>this</code>
+ * future, but the function's future instead.
+ * <p>
+ * If the function's future or {@link RunContext} are cancelled, this does not imply that <code>this</code> future is
+ * cancelled as well. Propagation of cancellation works only the other way round.
+ *
+ * @param function
+ * the function to be executed upon completion of <code>this</code> future, and is invoked only if neither
+ * <code>this</code> future, nor the function's future, nor the {@link RunMonitor} associated with the
+ * function's job input is cancelled.
+ * <p>
+ * The function's first argument is provided with <code>this</code> future's result and the second with
+ * <code>this</code> future's exception (if any).
+ * @param input
+ * input to schedule the function.
+ * @return the future representing the asynchronous execution of the function.
+ */
+ <FUNCTION_RESULT> IFuture<FUNCTION_RESULT> whenDoneSchedule(IBiFunction<RESULT, Throwable, FUNCTION_RESULT> function, JobInput input);
+
+ /**
* Registers the given listener to be notified about all job lifecycle events related to this Future. If the listener
* is already registered, that previous registration is replaced.
*
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/CompletionPromise.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/CompletionPromise.java
index 76b2956..4bc7aa9 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/CompletionPromise.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/CompletionPromise.java
@@ -85,8 +85,6 @@
}
// Notify registered handlers asynchronously.
- // Notice: Do not notify via Jobs.schedule(...), because for every job, JobManager registers a whenDone handler as well
- // in order to unregister the associated Quartz Trigger. Otherwise, that could cause an infinite recursion.
if (!m_handlers.isEmpty()) {
m_executor.execute(new Runnable() {
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/JobFutureTask.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/JobFutureTask.java
index 5c03ab8..e5abebb 100644
--- a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/JobFutureTask.java
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/job/internal/JobFutureTask.java
@@ -23,23 +23,29 @@
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.scout.rt.platform.BEANS;
import org.eclipse.scout.rt.platform.annotations.Internal;
import org.eclipse.scout.rt.platform.chain.callable.CallableChain;
import org.eclipse.scout.rt.platform.chain.callable.ICallableDecorator;
import org.eclipse.scout.rt.platform.context.RunContext;
+import org.eclipse.scout.rt.platform.context.RunContexts;
import org.eclipse.scout.rt.platform.context.RunMonitor;
+import org.eclipse.scout.rt.platform.exception.DefaultExceptionTranslator;
import org.eclipse.scout.rt.platform.exception.DefaultRuntimeExceptionTranslator;
import org.eclipse.scout.rt.platform.exception.IExceptionTranslator;
+import org.eclipse.scout.rt.platform.exception.NullExceptionTranslator;
import org.eclipse.scout.rt.platform.exception.PlatformException;
import org.eclipse.scout.rt.platform.filter.IFilter;
+import org.eclipse.scout.rt.platform.job.DoneEvent;
import org.eclipse.scout.rt.platform.job.ExecutionTrigger;
import org.eclipse.scout.rt.platform.job.IDoneHandler;
import org.eclipse.scout.rt.platform.job.IExecutionSemaphore;
import org.eclipse.scout.rt.platform.job.IFuture;
import org.eclipse.scout.rt.platform.job.JobInput;
import org.eclipse.scout.rt.platform.job.JobState;
+import org.eclipse.scout.rt.platform.job.Jobs;
import org.eclipse.scout.rt.platform.job.listener.IJobListener;
import org.eclipse.scout.rt.platform.job.listener.JobEvent;
import org.eclipse.scout.rt.platform.job.listener.JobEventData;
@@ -47,6 +53,7 @@
import org.eclipse.scout.rt.platform.util.Assertions;
import org.eclipse.scout.rt.platform.util.IRegistrationHandle;
import org.eclipse.scout.rt.platform.util.ToStringBuilder;
+import org.eclipse.scout.rt.platform.util.concurrent.IBiFunction;
import org.quartz.Calendar;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
@@ -430,6 +437,71 @@
}
@Override
+ public <FUNCTION_RESULT> IFuture<FUNCTION_RESULT> whenDoneSchedule(final IBiFunction<RESULT, Throwable, FUNCTION_RESULT> function, final JobInput input) {
+ Assertions.assertNotNull(input, "Input must not be null");
+ Assertions.assertNotNull(function, "Function must not be null");
+
+ final IFuture<RESULT> currentFuture = this;
+
+ // Create an execution guard to allocate a worker thread only after entering done state of this future.
+ final IExecutionSemaphore executionGuard = Jobs.newExecutionSemaphore(0);
+ whenDone(new IDoneHandler<RESULT>() {
+
+ @Override
+ public void onDone(final DoneEvent<RESULT> event) {
+ executionGuard.withPermits(1);
+ }
+ }, null);
+
+ // Upon cancellation of this future, cancel the function's future as well.
+ final RunMonitor functionRunMonitor = BEANS.get(RunMonitor.class);
+ m_runMonitor.registerCancellable(functionRunMonitor);
+
+ // Upon cancellation of the function's RunMonitor, cancel the function's future as well (to reflect proper cancellation status).
+ if (input.getRunContext() != null) {
+ input.getRunContext().getRunMonitor().registerCancellable(functionRunMonitor);
+ }
+
+ // Schedule the job to be executed upon this future enters done state.
+ // However, the execution guard ensures that a worker thread is only allocated upon this future enters done state.
+ return Jobs.schedule(new Callable<FUNCTION_RESULT>() {
+
+ @Override
+ public FUNCTION_RESULT call() throws Exception {
+ // Sanity check for cancellation because cancellation may not be percolated yet.
+ if (currentFuture.isCancelled()) {
+ IFuture.CURRENT.get().cancel(false);
+ return null;
+ }
+
+ // At this point, the depending future finished without being cancelled.
+ final AtomicReference<RESULT> rRef = new AtomicReference<>();
+ final AtomicReference<Throwable> tRef = new AtomicReference<>();
+ try {
+ rRef.set(currentFuture.awaitDoneAndGet(NullExceptionTranslator.class));
+ }
+ catch (final Throwable t) { // NOSONAR
+ tRef.set(BEANS.get(DefaultExceptionTranslator.class).unwrap(t));
+ }
+
+ // Schedule the job to run the function in the proper context (RunContext, ExecutionSemaphore, ExecutionHints, ...).
+ // However, if the function's RunContext is cancelled, it will not be executed.
+ return Jobs.schedule(new Callable<FUNCTION_RESULT>() {
+
+ @Override
+ public FUNCTION_RESULT call() throws Exception {
+ return function.apply(rRef.get(), tRef.get());
+ }
+ }, input).awaitDoneAndGet(DefaultExceptionTranslator.class);
+ }
+ }, Jobs.newInput()
+ .withName("[jobmanager] Waiting for 'whenDoneFunction' to execute [job={}, function={}, functionJobName={}]", getJobInput().getName(), function, input.getName())
+ .withExceptionHandling(null, false) // propagate a potential exception without handling it
+ .withRunContext(RunContexts.empty().withRunMonitor(functionRunMonitor))
+ .withExecutionSemaphore(executionGuard));
+ }
+
+ @Override
public IRegistrationHandle addListener(final IJobListener listener) {
return addListener(null, listener);
}
@@ -627,7 +699,7 @@
* Creates the Quartz Trigger to fire execution.
*/
protected OperableTrigger createQuartzTrigger(final JobInput input) {
- TriggerBuilder<Trigger> builder = TriggerBuilder.newTrigger()
+ final TriggerBuilder<Trigger> builder = TriggerBuilder.newTrigger()
.forJob(JobFutureTask.class.getSimpleName());
final ExecutionTrigger executionTrigger = input.getExecutionTrigger();
diff --git a/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/util/concurrent/IBiFunction.java b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/util/concurrent/IBiFunction.java
new file mode 100644
index 0000000..fc21db2
--- /dev/null
+++ b/org.eclipse.scout.rt.platform/src/main/java/org/eclipse/scout/rt/platform/util/concurrent/IBiFunction.java
@@ -0,0 +1,23 @@
+package org.eclipse.scout.rt.platform.util.concurrent;
+
+/**
+ * Represents a function that accepts two arguments and produces a result. This is the two-arity specialization of
+ * <code>Function</code>.
+ * <p>
+ * This is a surrogate for <code>BiFunction</code> contained in Java 8, and is to conform with Java 7 compliance.
+ *
+ * @param <T>
+ * the type of the first argument to the function
+ * @param <U>
+ * the type of the second argument to the function
+ * @param <R>
+ * the type of the result of the function
+ */
+public interface IBiFunction<T, U, R> {
+
+
+ /**
+ * Applies this function to the given argument.
+ */
+ R apply(T t, U u);
+}