Bug 573093: StackWalker and static methods to easily create Status

Change-Id: I711631db703fcc87ab37b8704a941b3c213f555b
Signed-off-by: Jonah Graham <jonah@kichwacoders.com>
Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.bundles/+/179700
Tested-by: Thomas Watson <tjwatson@us.ibm.com>
Reviewed-by: Thomas Watson <tjwatson@us.ibm.com>
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 692ade2..fe5a5aa 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
@@ -14,6 +14,8 @@
  *******************************************************************************/
 package org.eclipse.equinox.common.tests;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.URI;
@@ -234,4 +236,55 @@
 
 	}
 
+	public void testInfo() {
+		Status info = Status.info("message");
+		assertEquals(IStatus.INFO, info.getSeverity());
+		assertEquals("message", info.getMessage());
+		assertEquals(IStatus.OK, info.getCode());
+		assertEquals("org.eclipse.equinox.common.tests", info.getPlugin());
+		assertNull(info.getException());
+		assertArrayEquals(new IStatus[] {}, info.getChildren());
+	}
+
+	public void testWarning() {
+		Status warning = Status.warning("message");
+		assertEquals(IStatus.WARNING, warning.getSeverity());
+		assertEquals("message", warning.getMessage());
+		assertEquals(IStatus.OK, warning.getCode());
+		assertEquals("org.eclipse.equinox.common.tests", warning.getPlugin());
+		assertNull(warning.getException());
+		assertArrayEquals(new IStatus[] {}, warning.getChildren());
+	}
+
+	public void testWarningWithException() {
+
+		Status warningWithException = Status.warning("message", new Exception("exception"));
+		assertEquals(IStatus.WARNING, warningWithException.getSeverity());
+		assertEquals("message", warningWithException.getMessage());
+		assertEquals(IStatus.OK, warningWithException.getCode());
+		assertEquals("org.eclipse.equinox.common.tests", warningWithException.getPlugin());
+		assertEquals("exception", warningWithException.getException().getMessage());
+		assertArrayEquals(new IStatus[] {}, warningWithException.getChildren());
+	}
+
+	public void testError() {
+		Status error = Status.error("message");
+		assertEquals(IStatus.ERROR, error.getSeverity());
+		assertEquals("message", error.getMessage());
+		assertEquals(IStatus.OK, error.getCode());
+		assertEquals("org.eclipse.equinox.common.tests", error.getPlugin());
+		assertNull(error.getException());
+		assertArrayEquals(new IStatus[] {}, error.getChildren());
+	}
+
+	public void testErrorWithException() {
+		Status errorWithException = Status.error("message", new Exception("exception"));
+		assertEquals(IStatus.ERROR, errorWithException.getSeverity());
+		assertEquals("message", errorWithException.getMessage());
+		assertEquals(IStatus.OK, errorWithException.getCode());
+		assertEquals("org.eclipse.equinox.common.tests", errorWithException.getPlugin());
+		assertEquals("exception", errorWithException.getException().getMessage());
+		assertArrayEquals(new IStatus[] {}, errorWithException.getChildren());
+	}
+
 }
diff --git a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java
index f7124e1..d033aa2 100644
--- a/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java
+++ b/bundles/org.eclipse.equinox.common/src/org/eclipse/core/runtime/IStatus.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -56,18 +56,23 @@
 	/** Status type severity (bit mask, value 1) indicating this status is informational only.
 	 * @see #getSeverity()
 	 * @see #matches(int)
+	 * @see Status#info(String)
 	 */
 	public static final int INFO = 0x01;
 
 	/** Status type severity (bit mask, value 2) indicating this status represents a warning.
 	 * @see #getSeverity()
 	 * @see #matches(int)
+	 * @see Status#warning(String)
+	 * @see Status#warning(String, Throwable)
 	 */
 	public static final int WARNING = 0x02;
 
 	/** Status type severity (bit mask, value 4) indicating this status represents an error.
 	 * @see #getSeverity()
 	 * @see #matches(int)
+	 * @see Status#error(String)
+	 * @see Status#error(String, Throwable)
 	 */
 	public static final int ERROR = 0x04;
 
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 aad490f..54311fa 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, 2020 IBM Corporation and others.
+ * Copyright (c) 2000, 2021 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -24,6 +24,12 @@
  * <p>
  * This class can be used without OSGi running.
  * </p>
+ * <p>
+ * For performance critical code, such as when {@link Status} objects are not created
+ * for logging or error handling, there may be a small performance penalty when using
+ * static methods or constructors that automatically determine the plug-in id from
+ * the provided {@link Class} or by using {@link StackWalker}.
+ * </p>
  */
 public class Status implements IStatus {
 
@@ -76,6 +82,117 @@
 	 */
 	private static final IStatus[] theEmptyStatusArray = new IStatus[0];
 
+	private static StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+
+	/**
+	 * Construct a new status object with severity {@link IStatus#INFO},
+	 * code {@link IStatus#OK}, and pluginId determined automatically
+	 * from the calling class using {@link StackWalker}.
+	 * The created status has no children.
+	 *
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @return the newly created status
+	 * @since 3.15
+	 */
+	public static Status info(String message) {
+		Class<?> callerClass = null;
+		try {
+			callerClass = walker.getCallerClass();
+		} catch (IllegalCallerException e) {
+			// ignored - use null class
+		}
+		return new Status(INFO, callerClass, OK, message, null);
+	}
+
+	/**
+	 * Construct a new status object with severity {@link IStatus#WARNING},
+	 * code {@link IStatus#OK}, and pluginId determined automatically
+	 * from the calling class using {@link StackWalker}.
+	 * The created status has no children.
+	 *
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @return the newly created status
+	 * @since 3.15
+	 */
+	public static Status warning(String message) {
+		Class<?> callerClass = null;
+		try {
+			callerClass = walker.getCallerClass();
+		} catch (IllegalCallerException e) {
+			// ignored - use null class
+		}
+		return new Status(WARNING, callerClass, OK, message, null);
+	}
+
+	/**
+	 * Construct a new status object with severity {@link IStatus#WARNING},
+	 * code {@link IStatus#OK}, and pluginId determined automatically
+	 * from the calling class using {@link StackWalker}.
+	 * The created status has no children.
+	 *
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable
+	 * @return the newly created status
+	 * @since 3.15
+	 */
+	public static Status warning(String message, Throwable exception) {
+		Class<?> callerClass = null;
+		try {
+			callerClass = walker.getCallerClass();
+		} catch (IllegalCallerException e) {
+			// ignored - use null class
+		}
+		return new Status(WARNING, callerClass, OK, message, exception);
+	}
+
+	/**
+	 * Construct a new status object with severity {@link IStatus#ERROR},
+	 * code {@link IStatus#OK}, and pluginId determined automatically
+	 * from the calling class using {@link StackWalker}.
+	 * The created status has no children.
+	 *
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @return the newly created status
+	 * @since 3.15
+	 */
+	public static Status error(String message) {
+		Class<?> callerClass = null;
+		try {
+			callerClass = walker.getCallerClass();
+		} catch (IllegalCallerException e) {
+			// ignored - use null class
+		}
+		return new Status(ERROR, callerClass, OK, message, null);
+	}
+
+	/**
+	 * Construct a new status object with severity {@link IStatus#ERROR},
+	 * code {@link IStatus#OK}, and pluginId determined automatically
+	 * from the calling class using {@link StackWalker}.
+	 * The created status has no children.
+	 *
+	 * @param message a human-readable message, localized to the
+	 *    current locale
+	 * @param exception a low-level exception, or <code>null</code> if not
+	 *    applicable
+	 * @return the newly created status
+	 * @since 3.15
+	 */
+	public static Status error(String message, Throwable exception) {
+		Class<?> callerClass = null;
+		try {
+			callerClass = walker.getCallerClass();
+		} catch (IllegalCallerException e) {
+			// ignored - use null class
+		}
+		return new Status(ERROR, callerClass, OK, message, exception);
+	}
+
 	/**
 	 * Creates a new status object.  The created status has no children.
 	 *
@@ -208,7 +325,7 @@
 	 * @param caller the relevant class to build unique identifier from
 	 * @return identifier extracted for the given class
 	 */
-	private String identifier(Class<?> caller) {
+	private static String identifier(Class<?> caller) {
 		return Optional.ofNullable(caller)//
 				.flatMap(c -> Optional.ofNullable(FrameworkUtil.getBundle(c)))//
 				.map(b -> b.getSymbolicName())//