Merge "Bug 544155: [History Editor] Add option to remember answer to "add to history" prompt" into develop
diff --git a/plugins/org.eclipse.emf.edapt.common/src/org/eclipse/emf/edapt/internal/common/URIUtils.java b/plugins/org.eclipse.emf.edapt.common/src/org/eclipse/emf/edapt/internal/common/URIUtils.java
index 9a2472c..0924764 100644
--- a/plugins/org.eclipse.emf.edapt.common/src/org/eclipse/emf/edapt/internal/common/URIUtils.java
+++ b/plugins/org.eclipse.emf.edapt.common/src/org/eclipse/emf/edapt/internal/common/URIUtils.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2010 BMW Car IT, Technische Universitaet Muenchen, and others.
+ * Copyright (c) 2007, 2019 BMW Car IT, Technische Universitaet Muenchen, Christian W. Damus, and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -9,6 +9,7 @@
  * BMW Car IT - Initial API and implementation
  * Technische Universitaet Muenchen - Major refactoring and extension
  * Johannes Faltermeier - Extension
+ * Christian W. Damus - bug 544147
  *******************************************************************************/
 package org.eclipse.emf.edapt.internal.common;
 
@@ -16,9 +17,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigInteger;
 import java.net.MalformedURLException;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
@@ -44,6 +48,9 @@
  */
 public final class URIUtils {
 
+	// Pattern matching a version number embedded in an URI segment
+	private static final Pattern VERSION_NUMBER_PATTERN = Pattern.compile("(?<=\\bv?|[-_])\\d+\\b"); //$NON-NLS-1$
+
 	/**
 	 * Constructor
 	 */
@@ -223,4 +230,32 @@
 	public static URI getRelativePath(URI uri, URI relativeTo) {
 		return uri.deresolve(relativeTo, true, true, true);
 	}
+
+	/**
+	 * Increment the first part of a version number in a URI segment, as might
+	 * be in a package namespace URI, if it appears to contain a version number.
+	 *
+	 * @param segment an URI segment
+	 * @return the incremented {@code segment}, or just the {@code segment} as is
+	 *         if it does not appear to be or to contain a discrete a version number
+	 */
+	public static String incrementVersionSegment(String segment) {
+		String result = segment;
+
+		final Matcher m = VERSION_NUMBER_PATTERN.matcher(segment);
+		if (m.find()) {
+			final StringBuffer newSegment = new StringBuffer();
+
+			// Who knows how many digits there may be? Handle them all
+			final BigInteger version = new BigInteger(m.group());
+			final BigInteger newVersion = version.add(BigInteger.ONE);
+
+			m.appendReplacement(newSegment, newVersion.toString());
+			m.appendTail(newSegment);
+			result = newSegment.toString();
+		}
+
+		return result;
+	}
+
 }
diff --git a/plugins/org.eclipse.emf.edapt.history.editor/src/org/eclipse/emf/edapt/history/instantiation/ui/ReleaseWizardPage.java b/plugins/org.eclipse.emf.edapt.history.editor/src/org/eclipse/emf/edapt/history/instantiation/ui/ReleaseWizardPage.java
index 53005fd..4d5c407 100644
--- a/plugins/org.eclipse.emf.edapt.history.editor/src/org/eclipse/emf/edapt/history/instantiation/ui/ReleaseWizardPage.java
+++ b/plugins/org.eclipse.emf.edapt.history.editor/src/org/eclipse/emf/edapt/history/instantiation/ui/ReleaseWizardPage.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007-2015 BMW Car IT, TUM, EclipseSource Muenchen GmbH, and others.
+ * Copyright (c) 2007-2019 BMW Car IT, TUM, EclipseSource Muenchen GmbH, Christian W. Damus, and others.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -8,6 +8,7 @@
  *
  * Contributors:
  * Johannes Faltermeier - initial API and implementation
+ * Christian W. Damus - bug 544147
  ******************************************************************************/
 package org.eclipse.emf.edapt.history.instantiation.ui;
 
@@ -17,6 +18,7 @@
 import java.util.Set;
 
 import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.edapt.internal.common.URIUtils;
 import org.eclipse.jface.layout.GridDataFactory;
 import org.eclipse.jface.layout.GridLayoutFactory;
 import org.eclipse.jface.resource.ImageDescriptor;
@@ -28,6 +30,7 @@
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
@@ -141,6 +144,15 @@
 		final Point point = composite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
 		scrolledComposite.setMinSize(point);
 
+		if (packages.size() > 1) {
+			// Create convenience buttons to select/deselect all packages
+			final Composite buttonsComposite = new Composite(mainComposite, SWT.NONE);
+			GridDataFactory.swtDefaults().align(SWT.END, SWT.BEGINNING).applyTo(buttonsComposite);
+			buttonsComposite.setLayout(new FillLayout(SWT.HORIZONTAL));
+			createSelectAllButton(buttonsComposite, "Select &All", true); //$NON-NLS-1$
+			createSelectAllButton(buttonsComposite, "&Deselect All", false); //$NON-NLS-1$
+		}
+
 		setControl(mainComposite);
 
 		checkIfPageComplete();
@@ -186,9 +198,49 @@
 		});
 	}
 
+	private Button createSelectAllButton(Composite parent, final String label, final boolean select) {
+		final Button result = new Button(parent, SWT.PUSH);
+		result.setText(label);
+		result.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(SelectionEvent e) {
+				selectAll(select);
+			}
+		});
+		return result;
+	}
+
+	private void selectAll(boolean select) {
+		for (final Map.Entry<EPackage, Button> next : packageToUpdateButton.entrySet()) {
+			final EPackage ePackage = next.getKey();
+			final Button checkbox = next.getValue();
+
+			if (checkbox.getSelection() != select) {
+				checkbox.setSelection(select);
+				setTextEnablement(checkbox, ePackage);
+			}
+		}
+
+		checkIfPageComplete();
+	}
+
 	private void setTextEnablement(final Button updateButton, final EPackage ePackage) {
-		packageToSourceText.get(ePackage).setEnabled(updateButton.getSelection());
-		packageToTargetText.get(ePackage).setEnabled(updateButton.getSelection());
+		final boolean selected = updateButton.getSelection();
+		final Text sourceText = packageToSourceText.get(ePackage);
+		final Text targetText = packageToTargetText.get(ePackage);
+
+		sourceText.setEnabled(selected);
+		targetText.setEnabled(selected);
+
+		// Can we pre-populate the target text?
+		final String target = targetText.getText().trim();
+		if (selected && target.isEmpty()) {
+			final String source = sourceText.getText().trim();
+			final String newTarget = URIUtils.incrementVersionSegment(source);
+			if (!source.equals(newTarget)) {
+				targetText.setText(newTarget);
+			}
+		}
 	}
 
 	private void checkIfPageComplete() {
diff --git a/tests/org.eclipse.emf.edapt.common.tests/src/org/eclipse/emf/edapt/internal/common/URIUtilsTest.java b/tests/org.eclipse.emf.edapt.common.tests/src/org/eclipse/emf/edapt/internal/common/URIUtilsTest.java
index bae32a2..7d90c34 100644
--- a/tests/org.eclipse.emf.edapt.common.tests/src/org/eclipse/emf/edapt/internal/common/URIUtilsTest.java
+++ b/tests/org.eclipse.emf.edapt.common.tests/src/org/eclipse/emf/edapt/internal/common/URIUtilsTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007-2015 BMW Car IT, TUM, EclipseSource Muenchen GmbH, and others.
+ * Copyright (c) 2007-2019 BMW Car IT, TUM, EclipseSource Muenchen GmbH, Christian W. Damus, and others.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -8,40 +8,103 @@
  *
  * Contributors:
  * Johannes Faltermeier - initial API and implementation
+ * Christian W. Damus - bug 544147
  ******************************************************************************/
 package org.eclipse.emf.edapt.internal.common;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.util.Arrays;
 import java.util.Scanner;
 
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl;
 import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Enclosed.class)
 public class URIUtilsTest {
 
-	@Test
-	public void testGetJavaFileWithPlatformPluginURI() throws FileNotFoundException {
-		Scanner scanner = null;
-		try {
-			final URI uri = URI.createPlatformPluginURI("org.eclipse.emf.edapt.common.tests/resources/resource", false); //$NON-NLS-1$
-			assertTrue(new ExtensibleURIConverterImpl().exists(uri, null));
-			final File javaFile = URIUtils.getJavaFile(uri);
-			assertNotNull(javaFile);
-			scanner = new Scanner(javaFile);
-			scanner.useDelimiter("\\Z"); //$NON-NLS-1$
-			final String content = scanner.next();
-			assertEquals("foo", content); //$NON-NLS-1$
-		} finally {
-			if (scanner != null) {
-				scanner.close();
+	/**
+	 * Core tests.
+	 */
+	public static class Core {
+
+		@Test
+		public void testGetJavaFileWithPlatformPluginURI() throws FileNotFoundException {
+			Scanner scanner = null;
+			try {
+				final URI uri = URI.createPlatformPluginURI("org.eclipse.emf.edapt.common.tests/resources/resource", //$NON-NLS-1$
+					false);
+				assertTrue(new ExtensibleURIConverterImpl().exists(uri, null));
+				final File javaFile = URIUtils.getJavaFile(uri);
+				assertNotNull(javaFile);
+				scanner = new Scanner(javaFile);
+				scanner.useDelimiter("\\Z"); //$NON-NLS-1$
+				final String content = scanner.next();
+				assertEquals("foo", content); //$NON-NLS-1$
+			} finally {
+				if (scanner != null) {
+					scanner.close();
+				}
 			}
 		}
 
 	}
+
+	/**
+	 * Test cases for the {@link URIUtils#incrementVersionSegment(String)} API.
+	 */
+	@RunWith(Parameterized.class)
+	public static class IncrementVersionSegment {
+		private final String input;
+		private final String expected;
+
+		/**
+		 * Initializes me with the {@code input} segment and the {@code expected} result.
+		 *
+		 * @param input the segment to increment
+		 * @param expected the expected result
+		 */
+		public IncrementVersionSegment(String input, String expected) {
+			super();
+
+			this.input = input;
+			this.expected = expected;
+		}
+
+		@Test
+		public void increment() {
+			assertThat(URIUtils.incrementVersionSegment(input), is(expected));
+		}
+
+		@Parameters(name = "{0}")
+		@SuppressWarnings("nls")
+		public static Iterable<Object[]> parameters() {
+			return Arrays.asList(new Object[][] {
+				{ "1", "2" },
+				{ "v1", "v2" },
+				{ "1.0.0", "2.0.0" },
+				{ "v1.0.0", "v2.0.0" },
+				{ "beta_1", "beta_2" },
+				{ "beta-1.0.0", "beta-2.0.0" },
+				{ "dev1", "dev1" }, // Not like "v1"
+				{ "dev1_1.0.0", "dev1_2.0.0" },
+				{ "jar.1.0.0", "jar.2.0.0" },
+				{ "v123456789012345678901234567890", "v123456789012345678901234567891" },
+				{ "SNAPSHOT", "SNAPSHOT" },
+			});
+		}
+
+	}
+
 }