Bug 561712 - Status constructors to accept Class<?> as identifier

Added method to extract identifier from class
Added new constructors to Status
Added new constructors to MultiStatus
Added unit tests for new constructors

Change-Id: Ide1b26b760b72219ca62e8d43396deb2cf18dc59
Signed-off-by: Alexander Fedorov <alexander.fedorov@arsysop.ru>
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
index afab99b..70e8036 100644
--- a/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.common.tests/META-INF/MANIFEST.MF
@@ -2,12 +2,12 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: Common Eclipse Runtime Tests
 Bundle-SymbolicName: org.eclipse.equinox.common.tests;singleton:=true
-Bundle-Version: 3.11.0.qualifier
+Bundle-Version: 3.11.100.qualifier
 Automatic-Module-Name: org.eclipse.equinox.common.tests
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ActivationPolicy: lazy
 Require-Bundle: org.junit,
- org.eclipse.equinox.common;bundle-version="3.10.300",
+ org.eclipse.equinox.common;bundle-version="3.12.0",
  org.eclipse.core.tests.harness;bundle-version="3.11.400",
  org.eclipse.equinox.registry;bundle-version="3.8.200"
 Import-Package: org.eclipse.osgi.service.localization,
diff --git a/bundles/org.eclipse.equinox.common.tests/pom.xml b/bundles/org.eclipse.equinox.common.tests/pom.xml
index b4e3cde..b591f29 100644
--- a/bundles/org.eclipse.equinox.common.tests/pom.xml
+++ b/bundles/org.eclipse.equinox.common.tests/pom.xml
@@ -19,6 +19,6 @@
   </parent>
   <groupId>org.eclipse.equinox</groupId>
   <artifactId>org.eclipse.equinox.common.tests</artifactId>
-  <version>3.11.0-SNAPSHOT</version>
+  <version>3.11.100-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 </project>
diff --git a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
index 172214e..41e4405 100644
--- a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
+++ b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/StatusTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,12 +10,30 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Alexander Fedorov (ArSysOp) - Bug 561712
  *******************************************************************************/
 package org.eclipse.equinox.common.tests;
 
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Arrays;
-import org.eclipse.core.runtime.*;
+import java.util.Collections;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.Status;
 import org.eclipse.core.tests.harness.CoreTest;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
 
 public class StatusTest extends CoreTest {
 
@@ -108,6 +126,40 @@
 
 	}
 
+	public void testSingleFromClass() throws ClassNotFoundException, IOException, BundleException {
+		assertEquals("org.eclipse.equinox.common.tests", new Status(IStatus.WARNING, StatusTest.class, "").getPlugin());
+		assertEquals("org.eclipse.equinox.common", new Status(IStatus.ERROR, IStatus.class, "", null).getPlugin());
+		assertEquals("java.lang.String", new Status(IStatus.WARNING, String.class, 0, "", null).getPlugin());
+		assertEquals("org.eclipse.core.runtime.Status", new Status(IStatus.WARNING, (Class<?>) null, "").getPlugin());
+		assertEquals(TestClass.class.getName(),
+				new Status(IStatus.WARNING, installNoBSNBundle().loadClass(TestClass.class.getName()), "").getPlugin());
+	}
+
+	public void testMultiFromClass() throws ClassNotFoundException, IOException, BundleException {
+		assertEquals("org.eclipse.equinox.common", new MultiStatus(IStatus.class, 0, "").getPlugin());
+		assertEquals("org.eclipse.equinox.common.tests", new MultiStatus(StatusTest.class, 0, "").getPlugin());
+		assertEquals("java.lang.String", new MultiStatus(String.class, 0, new Status[0], "", null).getPlugin());
+		assertEquals("org.eclipse.core.runtime.MultiStatus", new MultiStatus((Class<?>) null, 0, "").getPlugin());
+		assertEquals(TestClass.class.getName(),
+				new MultiStatus(installNoBSNBundle().loadClass(TestClass.class.getName()), 0, "").getPlugin());
+	}
+
+	private Bundle installNoBSNBundle() throws IOException, BundleException {
+		BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+		File noNameBSNFile = bc.getDataFile("noNameBSN.jar");
+		noNameBSNFile.delete();
+		URI noNameBSNJar = URI.create("jar:" + noNameBSNFile.toURI().toASCIIString());
+
+		try (FileSystem zipfs = FileSystems.newFileSystem(noNameBSNJar, Collections.singletonMap("create", "true"))) {
+			URL testClassURL = getClass().getResource("TestClass.class");
+			Path testClassPath = zipfs.getPath(testClassURL.getPath().substring(1));
+			// copy a file into the zip file
+			Files.createDirectories(testClassPath.getParent());
+			Files.copy(testClassURL.openStream(), testClassPath);
+		}
+		return bc.installBundle(noNameBSNFile.toURI().toASCIIString());
+	}
+
 	public void testAddAll() {
 
 		multistatus1.add(status2);
diff --git a/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/TestClass.java b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/TestClass.java
new file mode 100644
index 0000000..05299a2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.common.tests/src/org/eclipse/equinox/common/tests/TestClass.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2020 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Alexander Fedorov (ArSysOp) - Bug 561712
+ *******************************************************************************/
+package org.eclipse.equinox.common.tests;
+
+public class TestClass {
+
+}
diff --git a/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
index b708fc2..d9d7f32 100644
--- a/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.common/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.equinox.common; singleton:=true
-Bundle-Version: 3.11.0.qualifier
+Bundle-Version: 3.12.0.qualifier
 Bundle-Localization: plugin
 Export-Package: org.eclipse.core.internal.boot;x-friends:="org.eclipse.core.resources,org.eclipse.pde.build",
  org.eclipse.core.internal.runtime;common=split;mandatory:=common;
diff --git a/bundles/org.eclipse.equinox.common/pom.xml b/bundles/org.eclipse.equinox.common/pom.xml
index 1e656fa..696e75b 100644
--- a/bundles/org.eclipse.equinox.common/pom.xml
+++ b/bundles/org.eclipse.equinox.common/pom.xml
@@ -19,6 +19,6 @@
   </parent>
   <groupId>org.eclipse.equinox</groupId>
   <artifactId>org.eclipse.equinox.common</artifactId>
-  <version>3.11.0-SNAPSHOT</version>
+  <version>3.12.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
 </project>
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java
index b7a1c21..6628d20 100644
--- a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/MultiStatus.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Alexander Fedorov (ArSysOp) - Bug 561712
  *******************************************************************************/
 package org.eclipse.core.runtime;
 
@@ -31,6 +32,25 @@
 	/**
 	 * Creates and returns a new multi-status object with the given children.
 	 *
+	 * @param caller the relevant class to build unique identifier from
+	 * @param code the caller-specific status code
+	 * @param newChildren the list of children status objects
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable 
+	 *    
+	 * @since 3.12
+	 */
+	public MultiStatus(Class<?> caller, int code, IStatus[] newChildren, String message, Throwable exception) {
+		this(caller, code, message, exception);
+		Assert.isLegal(newChildren != null);
+		addAllInternal(newChildren);
+	}
+
+	/**
+	 * Creates and returns a new multi-status object with the given children.
+	 *
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param code the plug-in-specific status code
 	 * @param newChildren the list of children status objects
@@ -48,6 +68,22 @@
 	/**
 	 * Creates and returns a new multi-status object with no children.
 	 *
+	 * @param caller the relevant class to build unique identifier from
+	 * @param code the caller-specific status code
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable 
+	 *    
+	 * @since 3.12
+	 */
+	public MultiStatus(Class<?> caller, int code, String message, Throwable exception) {
+		super(OK, caller, code, message, exception);
+	}
+
+	/**
+	 * Creates and returns a new multi-status object with no children.
+	 *
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param code the plug-in-specific status code
 	 * @param message a human-readable message, localized to the
@@ -62,6 +98,19 @@
 	/**
 	 * Creates and returns a new multi-status object with no children.
 	 *
+	 * @param caller the relevant class to build unique identifier from
+	 * @param code the caller-specific status code
+	 * @param message a human-readable message, localized to the current locale 
+	 * 
+	 * @since 3.12
+	 */
+	public MultiStatus(Class<?> caller, int code, String message) {
+		super(OK, caller, code, message, null);
+	}
+
+	/**
+	 * Creates and returns a new multi-status object with no children.
+	 *
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param code the plug-in-specific status code
 	 * @param message a human-readable message, localized to the current locale 
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java
index fe1835a..aa63c3f 100644
--- a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/Status.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,10 +10,13 @@
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Alexander Fedorov (ArSysOp) - Bug 561712
  *******************************************************************************/
 package org.eclipse.core.runtime;
 
+import java.util.Optional;
 import org.eclipse.core.internal.runtime.LocalizationUtils;
+import org.osgi.framework.FrameworkUtil;
 
 /**
  * A concrete status implementation, suitable either for 
@@ -78,6 +81,28 @@
 	 *
 	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
 	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
+	 * @param caller the relevant class to build unique identifier from
+	 * @param code the caller-specific status code, or <code>OK</code>
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable
+	 *    
+	 * @since 3.12
+	 */
+	public Status(int severity, Class<?> caller, int code, String message, Throwable exception) {
+		setSeverity(severity);
+		setPlugin(identifier(caller));
+		setCode(code);
+		setMessage(message);
+		setException(exception);
+	}
+
+	/**
+	 * Creates a new status object.  The created status has no children.
+	 *
+	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
+	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param code the plug-in-specific status code, or <code>OK</code>
 	 * @param message a human-readable message, localized to the
@@ -99,6 +124,28 @@
 	 *
 	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
 	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
+	 * @param caller the relevant class to build unique identifier from
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable
+	 *     
+	 * @since 3.12
+	 */
+	public Status(int severity, Class<?> caller, String message, Throwable exception) {
+		setSeverity(severity);
+		setPlugin(identifier(caller));
+		setMessage(message);
+		setException(exception);
+		setCode(OK);
+	}
+
+	/**
+	 * Simplified constructor of a new status object; assumes that code is <code>OK</code>.
+	 * The created status has no children.
+	 *
+	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
+	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param message a human-readable message, localized to the
 	 *    current locale
@@ -121,6 +168,26 @@
 	 *
 	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
 	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
+	 * @param caller the relevant class to build unique identifier from
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 *    
+	 * @since 3.12
+	 */
+	public Status(int severity, Class<?> caller, String message) {
+		setSeverity(severity);
+		setPlugin(identifier(caller));
+		setMessage(message);
+		setCode(OK);
+		setException(null);
+	}
+
+	/**
+	 * Simplified constructor of a new status object; assumes that code is <code>OK</code> and
+	 * exception is <code>null</code>. The created status has no children.
+	 *
+	 * @param severity the severity; one of <code>OK</code>, <code>ERROR</code>, 
+	 * <code>INFO</code>, <code>WARNING</code>,  or <code>CANCEL</code>
 	 * @param pluginId the unique identifier of the relevant plug-in
 	 * @param message a human-readable message, localized to the
 	 *    current locale
@@ -135,6 +202,21 @@
 		setException(null);
 	}
 
+	/**
+	 * Extracts an identifier from the given class: either bundle symbolic name or class name, never returns <code>null</code>
+	 * 
+	 * @param caller the relevant class to build unique identifier from
+	 * @return identifier extracted for the given class
+	 */
+	private String identifier(Class<?> caller) {
+		return Optional.ofNullable(caller)//
+				.flatMap(c -> Optional.ofNullable(FrameworkUtil.getBundle(c)))//
+				.map(b -> b.getSymbolicName())//
+				.orElseGet(() -> Optional.ofNullable(caller)//
+						.map(c -> c.getName())//
+						.orElse(getClass().getName()));
+	}
+
 	@Override
 	public IStatus[] getChildren() {
 		return theEmptyStatusArray;