Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2015-02-23 21:41:32 +0000
committerChristian W. Damus2015-02-23 21:42:46 +0000
commita9e9e3f6b5b41941a139a87db1fdf3325d1684c2 (patch)
tree8659924606edabd969afc0269cfb22f340fc6d2a /plugins/developer/org.eclipse.papyrus.releng.tools
parentc030cb70b2145527e6736ac25ebe708447f75424 (diff)
downloadorg.eclipse.papyrus-a9e9e3f6b5b41941a139a87db1fdf3325d1684c2.tar.gz
org.eclipse.papyrus-a9e9e3f6b5b41941a139a87db1fdf3325d1684c2.tar.xz
org.eclipse.papyrus-a9e9e3f6b5b41941a139a87db1fdf3325d1684c2.zip
[Releng Tools] Dependency updater improvements
Improve user experience when cancelling at any stage. Add confirmation check for rolling back interim builds of EMF and similar projects (e.g., UML2) to the previous milestone. When multiple dependency updates in a file look suspicious, prompt for all at once in a single dialog instead of repeatedly putting up dialogs for each one.
Diffstat (limited to 'plugins/developer/org.eclipse.papyrus.releng.tools')
-rw-r--r--plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java13
-rw-r--r--plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java314
2 files changed, 290 insertions, 37 deletions
diff --git a/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java b/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java
index d48ac99e1f4..03b57af11ce 100644
--- a/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java
+++ b/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/handler/UpdateDependenciesHandler.java
@@ -34,6 +34,7 @@ import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
@@ -69,6 +70,7 @@ public class UpdateDependenciesHandler extends AbstractHandler {
Shell activeShell = HandlerUtil.getActiveShell(event);
List<IFile> updated = Lists.newArrayListWithExpectedSize(4);
IFile aggregationBuildFile = null;
+ boolean cancelled = false;
try {
if (selection instanceof IStructuredSelection) {
@@ -77,7 +79,9 @@ public class UpdateDependenciesHandler extends AbstractHandler {
if (!files.isEmpty()) {
List<IFile> aggregationBuildFiles = findAggregationBuildFiles();
aggregationBuildFile = chooseAggregationBuildFile(aggregationBuildFiles, activeShell);
- if (aggregationBuildFile != null) {
+ if (aggregationBuildFile == null) {
+ cancelled = true;
+ } else {
Aggregation aggregation = loadAggregationModel(aggregationBuildFile);
if (aggregation != null) {
Map<Object, Object> context = Maps.newHashMap();
@@ -90,13 +94,18 @@ public class UpdateDependenciesHandler extends AbstractHandler {
}
}
}
+ } catch (OperationCanceledException e) {
+ cancelled = true;
} catch (Exception e) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error", e)); //$NON-NLS-1$
MessageDialog.openError(activeShell, Messages.UpdateRMapAction_error, e.getLocalizedMessage());
}
if (updated.isEmpty()) {
- MessageDialog.openInformation(activeShell, "No Files Updated", "No files were updated for new dependencies.");
+ // Don't waste the user's attention on this if he cancelled
+ if (!cancelled) {
+ MessageDialog.openInformation(activeShell, "No Files Updated", "No files were updated for new dependencies.");
+ }
} else {
String fileList = Joiner.on(", ").join(Iterables.transform(updated, new Function<IFile, IPath>() {
@Override
diff --git a/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java b/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java
index 084f33624e2..8cba78c8113 100644
--- a/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java
+++ b/plugins/developer/org.eclipse.papyrus.releng.tools/src/org/eclipse/papyrus/releng/tools/internal/popup/actions/DependencyUpdater.java
@@ -15,7 +15,11 @@
package org.eclipse.papyrus.releng.tools.internal.popup.actions;
import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -37,24 +41,47 @@ import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.EList;
import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.papyrus.releng.tools.internal.Activator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.dialogs.ListSelectionDialog;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
public abstract class DependencyUpdater {
private final Pattern commentPattern = Pattern.compile("updateFrom\\s*\\(\\s*\"(.*?)\"\\s*,\\s*(\\d+)\\s*\\)"); //$NON-NLS-1$
- private final Pattern typicalBuildTimestampPattern = Pattern.compile("[NISMR](?:-\\d+\\.\\d+(?:\\.\\d+)?(?:M|RC)\\d[abcd]-)?20\\d\\d[-0-9]+"); //$NON-NLS-1$
+ private final List<LocationUpdateStrategy> locationUpdateStrategies = ImmutableList.of(
+ new MilestoneLocationStrategy(),
+ new EMFLocationStrategy());
public DependencyUpdater() {
super();
@@ -76,7 +103,7 @@ public abstract class DependencyUpdater {
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList uris = (NodeList) xpath.evaluate(getXpath(), documentElement, XPathConstants.NODESET);
-
+ List<UpdateItem> updates = Lists.newArrayList();
for (int i = 0; i < uris.getLength(); i++) {
Node uri = uris.item(i);
Node precedingComment = getPrecedingComment(uri);
@@ -90,17 +117,24 @@ public abstract class DependencyUpdater {
if (contribution == null) {
throw new RuntimeException("'updateFrom' failed: cannot find contribution with label \"" + contributionName + "\""); //$NON-NLS-1$ //$NON-NLS-2$
}
- updateWithContribution(parentShell, uri, contribution, repositoryIndex, context);
+ updates.add(new UpdateItem(uri, contribution, repositoryIndex));
} else if (comment.contains("updateFrom")) { //$NON-NLS-1$
throw new Exception("Wrong syntax for 'updateFrom' : should be " + getCommentSyntax()); //$NON-NLS-1$
}
}
}
- save(doc, rmapFile);
+ if (confirmUpdates(parentShell, updates, context)) {
+ for (UpdateItem next : updates) {
+ updateWithContribution(parentShell, next.uriNode, next.contribution, next.repositoryIndex, context);
+ }
- mapFile.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+ save(doc, rmapFile);
+ mapFile.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
+ }
+ } catch (OperationCanceledException e) {
+ throw e;
} catch (Exception e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error updating map: " + e.getLocalizedMessage(), e)); //$NON-NLS-1$
}
@@ -122,14 +156,8 @@ public abstract class DependencyUpdater {
+ ") : there are " + repositories.size() + " contributions"); //$NON-NLS-1$ //$NON-NLS-2$
}
String location = repositories.get(repositoryIndex).getLocation();
- String current = getCurrentLocation(uri);
- if ((current == null) || !current.equals(location)) {
- if ((current == null) || current.isEmpty()
- || isLocationSimilar(current, location) || promptToReplace(parentShell, contribution.getLabel(), current, location, context)) {
- updateUri(uri, location);
- }
- }
+ updateUri(uri, location);
}
protected abstract String getCurrentLocation(Node uri);
@@ -175,37 +203,253 @@ public abstract class DependencyUpdater {
protected abstract String getXpath();
- protected boolean isLocationSimilar(String oldLocation, String newLocation) {
- boolean result = true; // Optimistically assume sameness if we can't find any build timestamps
-
- Matcher oldMatcher = typicalBuildTimestampPattern.matcher(oldLocation);
- Matcher newMatcher = typicalBuildTimestampPattern.matcher(newLocation);
- boolean foundOld = oldMatcher.find();
- boolean foundNew = newMatcher.find();
-
- if (foundOld != foundNew) {
- // definitely different
- result = false;
- } else if (foundNew) {
- // Compare prefixes
- String oldPrefix = oldLocation.substring(0, oldMatcher.start());
- String newPrefix = newLocation.substring(0, newMatcher.start());
- result = newPrefix.equals(oldPrefix);
+ private boolean promptToReplaceSingle(Shell parentShell, LocationUpdate locationUpdate, Map<Object, Object> context) {
+ String message = NLS.bind("{0}\n\nUpdate anyways?", locationUpdate.strategy.getUpdateConfirmationMessage(locationUpdate.update, locationUpdate.oldLocation, locationUpdate.newLocation)); //$NON-NLS-1$
+ boolean result = MessageDialog.openQuestion(parentShell, "Confirm Location Update", message);
+ setReplace(locationUpdate.update, result, context);
+
+ return result;
+ }
+
+ private Boolean getReplace(UpdateItem update, Map<Object, Object> context) {
+ return (Boolean) context.get("$replace$::" + update.contribution.getLabel()); //$NON-NLS-1$
+ }
+
+ private void setReplace(UpdateItem update, Boolean replace, Map<Object, Object> context) {
+ context.put("$replace$::" + update.contribution.getLabel(), replace); //$NON-NLS-1$
+ }
+
+ /**
+ * Prompt to confirm multiple suspicious dependency replacements, returning those updates that the
+ * user confirms to perform.
+ */
+ private Collection<LocationUpdate> promptToReplaceMultiple(Shell parentShell, Collection<? extends LocationUpdate> locationUpdates, Map<Object, Object> context) {
+ final List<LocationUpdate> result = Lists.newArrayList(locationUpdates);
+
+ ILabelProvider labels = new LabelProvider() {
+ @Override
+ public String getText(Object element) {
+ return ((LocationUpdate) element).update.contribution.getLabel();
+ }
+ };
+
+ ListSelectionDialog dialog = new ListSelectionDialog(parentShell, locationUpdates, ArrayContentProvider.getInstance(), labels, "Select dependencies to confirm updating.") {
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite result = (Composite) super.createDialogArea(parent);
+
+ createDetailsArea(result);
+
+ return result;
+ }
+
+ void createDetailsArea(Composite parent) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText("Details:");
+
+ final Text details = new Text(parent, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.WRAP | SWT.READ_ONLY);
+ GridData data = new GridData(GridData.FILL_BOTH);
+ data.heightHint = details.getLineHeight() * 4;
+ details.setLayoutData(data);
+
+ getViewer().addSelectionChangedListener(new ISelectionChangedListener() {
+
+ @Override
+ public void selectionChanged(SelectionChangedEvent event) {
+ IStructuredSelection sel = (IStructuredSelection) event.getSelection();
+ if (sel.isEmpty()) {
+ details.setText(""); //$NON-NLS-1$
+ } else {
+ LocationUpdate update = (LocationUpdate) sel.getFirstElement();
+ details.setText(update.strategy.getUpdateConfirmationMessage(update.update, update.oldLocation, update.newLocation));
+ }
+ }
+ });
+ }
+ };
+
+ dialog.setInitialElementSelections(result);
+ dialog.setTitle("Confirm Location Update");
+ if (dialog.open() == Window.OK) {
+ Set<?> toUpdate = ImmutableSet.copyOf(dialog.getResult());
+ for (Iterator<LocationUpdate> iter = result.iterator(); iter.hasNext();) {
+ LocationUpdate next = iter.next();
+
+ boolean update = toUpdate.contains(next);
+ setReplace(next.update, update, context);
+ if (!update) {
+ // User elects not to update, so remove from the result
+ iter.remove();
+ }
+ }
+ } else {
+ throw new OperationCanceledException();
}
return result;
}
- protected boolean promptToReplace(Shell parentShell, String contributionName, String oldLocation, String newLocation, Map<Object, Object> context) {
- String key = "$replace$::" + contributionName; //$NON-NLS-1$
- Boolean result = (Boolean) context.get(key);
+ private boolean confirmUpdates(final Shell parentShell, List<UpdateItem> updates, Map<Object, Object> context) {
+ Map<UpdateItem, LocationUpdate> toPrompt = Maps.newHashMap();
+
+ for (Iterator<UpdateItem> iter = updates.iterator(); iter.hasNext();) {
+ UpdateItem next = iter.next();
+
+ // Check for previous prompt answer
+ Boolean previousAnswer = getReplace(next, context);
+ if (previousAnswer != null) {
+ if (previousAnswer) {
+ // Don't prompt this one
+ } else {
+ iter.remove(); // Don't update this one
+ }
+ } else {
+ EList<MappedRepository> repositories = next.contribution.getRepositories();
+ if (next.repositoryIndex >= repositories.size()) {
+ throw new RuntimeException("wrong index in updateFrom(\"" + next.contribution.getLabel() + "\"" + next.repositoryIndex //$NON-NLS-1$ //$NON-NLS-2$
+ + ") : there are " + repositories.size() + " contributions"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ String location = repositories.get(next.repositoryIndex).getLocation();
+ String current = getCurrentLocation(next.uriNode);
+
+ if ((current == null) || !current.equals(location)) {
+ if ((current != null) && !current.isEmpty()) {
+ LocationUpdateStrategy strategy = findLocationUpdateStrategy(next, current, location);
+ if (strategy != null) {
+ toPrompt.put(next, new LocationUpdate(next, strategy, current, location));
+ }
+ }
+ }
+ }
+ }
+
+ if (!toPrompt.isEmpty()) {
+ if (toPrompt.size() == 1) {
+ // Simple dialog
+ UpdateItem update = Iterables.getOnlyElement(toPrompt.keySet());
+ LocationUpdate location = toPrompt.get(update);
+ if (!promptToReplaceSingle(parentShell, location, context)) {
+ updates.remove(update);
+ }
+ } else {
+ // List dialog
+ toPrompt.values().removeAll(promptToReplaceMultiple(parentShell, toPrompt.values(), context));
+ updates.removeAll(toPrompt.keySet()); // What remains are the update exclusions
+ }
+ }
+
+ return !updates.isEmpty();
+ }
+
+ private LocationUpdateStrategy findLocationUpdateStrategy(UpdateItem update, String oldLocation, String newLocation) {
+ LocationUpdateStrategy result = null;
- if (result == null) {
- String message = NLS.bind("The new location \"{0}\" for project \"{1}\" is not like the current location \"{2}\". This could roll back to a previous (obsolete) version. Update anyways?", new Object[] { newLocation, contributionName, oldLocation });
- result = MessageDialog.openQuestion(parentShell, "Confirm Location Update", message);
- context.put(key, result);
+ for (LocationUpdateStrategy next : locationUpdateStrategies) {
+ if (!next.shouldAutoUpdate(update, oldLocation, newLocation)) {
+ result = next;
+ break;
+ }
}
return result;
}
+
+ //
+ // Nested types
+ //
+
+ private static class UpdateItem {
+ final Node uriNode;
+ final Contribution contribution;
+ final int repositoryIndex;
+
+ UpdateItem(Node uriNode, Contribution contribution, int repositoryIndex) {
+ super();
+
+ this.uriNode = uriNode;
+ this.contribution = contribution;
+ this.repositoryIndex = repositoryIndex;
+ }
+ }
+
+ private static class LocationUpdate {
+ final UpdateItem update;
+ final LocationUpdateStrategy strategy;
+ final String oldLocation;
+ final String newLocation;
+
+ LocationUpdate(UpdateItem update, LocationUpdateStrategy strategy, String oldLocation, String newLocation) {
+ super();
+
+ this.update = update;
+ this.strategy = strategy;
+ this.oldLocation = oldLocation;
+ this.newLocation = newLocation;
+ }
+ }
+
+ private interface LocationUpdateStrategy {
+ boolean shouldAutoUpdate(UpdateItem update, String oldLocation, String newLocation);
+
+ String getUpdateConfirmationMessage(UpdateItem update, String oldLocation, String newLocation);
+ }
+
+ static class MilestoneLocationStrategy implements LocationUpdateStrategy {
+ private final Pattern typicalBuildTimestampPattern = Pattern.compile("[NISMR](?:-\\d+\\.\\d+(?:\\.\\d+)?(?:M|RC)\\d[abcd]-)?20\\d\\d[-0-9]+"); //$NON-NLS-1$
+
+ @Override
+ public boolean shouldAutoUpdate(UpdateItem update, String oldLocation, String newLocation) {
+ boolean result = true; // Optimistically assume sameness if we can't find any build timestamps
+
+ Matcher oldMatcher = typicalBuildTimestampPattern.matcher(oldLocation);
+ Matcher newMatcher = typicalBuildTimestampPattern.matcher(newLocation);
+ boolean foundOld = oldMatcher.find();
+ boolean foundNew = newMatcher.find();
+
+ if (foundOld != foundNew) {
+ // definitely different
+ result = false;
+ } else if (foundNew) {
+ // Compare prefixes
+ String oldPrefix = oldLocation.substring(0, oldMatcher.start());
+ String newPrefix = newLocation.substring(0, newMatcher.start());
+ result = newPrefix.equals(oldPrefix);
+ }
+
+ return result;
+ }
+
+ @Override
+ public String getUpdateConfirmationMessage(UpdateItem update, String oldLocation, String newLocation) {
+ return NLS.bind("The new location \"{0}\" for project \"{1}\" is not like the current location \"{2}\". This could roll back to an older (obsolete) build.", new Object[] { newLocation, update.contribution.getLabel(), oldLocation });
+ }
+ }
+
+ static class EMFLocationStrategy implements LocationUpdateStrategy {
+ private final Pattern typicalMilestonesPattern = Pattern.compile("\\d+\\.\\d+(milestones|interim)$"); //$NON-NLS-1$
+
+ @Override
+ public boolean shouldAutoUpdate(UpdateItem update, String oldLocation, String newLocation) {
+ boolean result = true; // Optimistically assume sameness if we can't find any milestones/interim segment
+
+ Matcher oldMatcher = typicalMilestonesPattern.matcher(oldLocation);
+ Matcher newMatcher = typicalMilestonesPattern.matcher(newLocation);
+ boolean foundOld = oldMatcher.find();
+ boolean foundNew = newMatcher.find();
+
+ if (foundOld != foundNew) {
+ // definitely different
+ result = false;
+ } else if (foundNew && !(oldMatcher.group(1).equals(newMatcher.group(1)))) {
+ result = (oldMatcher.group(1).equals("interim"));
+ }
+
+ return result;
+ }
+
+ @Override
+ public String getUpdateConfirmationMessage(UpdateItem update, String oldLocation, String newLocation) {
+ return NLS.bind("The current location \"{2}\" for project \"{1}\" provides interim builds. Updating from \"{0}\" could roll back to a previous milestone build.", new Object[] { newLocation, update.contribution.getLabel(), oldLocation });
+ }
+ }
}

Back to the top