Bug 502360 - Provide public Log API in org.eclipse.core.runtime

Change-Id: I775ed05e98442205facdf7b20d5a7cac9608fb88
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Log.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Log.java
new file mode 100644
index 0000000..dae7a10
--- /dev/null
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/runtime/Log.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Google, Inc. 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Stefan Xenos <sxenos@gmail.com> (Google) - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Contains utility functions for logging.
+ *
+ * @since 3.13
+ */
+public final class Log {
+	/**
+	 * Writes an error message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 * @param exception
+	 *            exception to log or null if none.
+	 */
+	public static void error(Object context, String message, Throwable exception) {
+		log(IStatus.ERROR, context, IStatus.OK, message, exception);
+	}
+
+	/**
+	 * Writes an error message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 */
+	public static void error(Object context, String message) {
+		log(IStatus.ERROR, context, IStatus.OK, message, null);
+	}
+
+	/**
+	 * Writes a warning message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 * @param exception
+	 *            exception to log or null if none.
+	 */
+	public static void warning(Object context, String message, Throwable exception) {
+		log(IStatus.WARNING, context, IStatus.OK, message, exception);
+	}
+
+	/**
+	 * Writes a warning message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 */
+	public static void warning(Object context, String message) {
+		log(IStatus.WARNING, context, IStatus.OK, message, null);
+	}
+
+	/**
+	 * Writes an info message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 * @param exception
+	 *            exception to log or null if none.
+	 */
+	public static void info(Object context, String message, Throwable exception) {
+		log(IStatus.INFO, context, IStatus.OK, message, exception);
+	}
+
+	/**
+	 * Writes an info message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param message
+	 *            message string to be logged.
+	 */
+	public static void info(Object context, String message) {
+		log(IStatus.INFO, context, IStatus.OK, message, null);
+	}
+
+	/**
+	 * Writes a status message to the log.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @param toLog
+	 *            status message to be logged.
+	 */
+	public static void log(Object context, IStatus toLog) {
+		getLog(context).log(toLog);
+	}
+
+	/**
+	 * Returns the {@link ILog} associated with the given context object.
+	 *
+	 * @param context
+	 *            a {@link Class} or object that was loaded from the plugin that
+	 *            generated the message or a {@link String} containing the
+	 *            bundle's symbolic name.
+	 * @return the {@link ILog} associated with the given context object.
+	 */
+	public static ILog getLog(Object context) {
+		Bundle bundle = getBundleForContext(context);
+		return Platform.getLog(bundle);
+	}
+
+	private static void log(int severity, Object context, int code, String message, Throwable exception) {
+		Bundle bundle = getBundleForContext(context);
+		String symbolicName = bundle.getSymbolicName();
+		Platform.getLog(bundle).log(new Status(severity, symbolicName, code, message, exception));
+	}
+
+	/**
+	 * Returns the bundle for the given object if the given object didn't come
+	 * from a plugin bundle. If the given context is a String, it is used as the
+	 * symbolic name of the bundle. If the given context is a Class, the bundle
+	 * that contains that class is returned. Otherwise, the class of the given
+	 * object is used.
+	 */
+	private static Bundle getBundleForContext(Object context) {
+		Bundle bundle;
+		if (context instanceof String) {
+			bundle = Platform.getBundle((String) context);
+			if (bundle == null) {
+				throw new IllegalArgumentException(
+						"Bundle not found while attempting to write to log. Invalid bundle id = " + context); //$NON-NLS-1$
+			}
+			return bundle;
+		}
+		Class classFromBundle;
+		if (context instanceof Class) {
+			classFromBundle = (Class) context;
+		} else {
+			classFromBundle = context.getClass();
+		}
+		bundle = FrameworkUtil.getBundle(classFromBundle);
+		if (bundle == null) {
+			throw new IllegalArgumentException("Context must be a Class or Object that was loaded from an Eclipse" //$NON-NLS-1$
+					+ " plugin bundle. Context was " + context); //$NON-NLS-1$
+		}
+		return bundle;
+	}
+}
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/AllTests.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/AllTests.java
index c3f41f7..5e7f509 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/AllTests.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/AllTests.java
@@ -28,6 +28,7 @@
 		suite.addTest(IAdapterManagerTest.suite());
 		suite.addTest(IAdapterManagerServiceTest.suite());
 		suite.addTest(AdapterManagerDynamicTest.suite());
+		suite.addTest(LogTest.suite());
 		suite.addTest(OperationCanceledExceptionTest.suite());
 		suite.addTest(PathTest.suite());
 		suite.addTest(PlatformTest.suite());
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/LogTest.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/LogTest.java
new file mode 100644
index 0000000..a15eab0
--- /dev/null
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/LogTest.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2015 IBM Corporation 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
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Stefan Xenos - initial API and implementation
+ *     Stefan Xenos - bug 174539 - add a 1-argument convert(...) method
+ *     Stefan Xenos - bug 174040 - SubMonitor#convert doesn't always set task name
+ *     Stefan Xenos - bug 206942 - Regression test for infinite progress reporting rate
+ *     IBM Corporation - bug 252446 - SubMonitor.newChild passes zero ticks to child
+ *     Alexander Kurtakov <akurtako@redhat.com> - bug 458490
+ *******************************************************************************/
+package org.eclipse.core.tests.runtime;
+
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.*;
+import org.eclipse.core.runtime.*;
+
+public class LogTest extends TestCase {
+	private static String testMessage;
+
+	private final List<IStatus> loggedStatus = new ArrayList<>();
+
+	private ILogListener logListener = new ILogListener() {
+		@Override
+		public void logging(IStatus status, String plugin) {
+			if (plugin.equals(RuntimeTestsPlugin.PI_RUNTIME_TESTS)) {
+				loggedStatus.add(status);
+			}
+		}
+	};
+
+	public LogTest() {
+		super();
+	}
+
+	public LogTest(String name) {
+		super(name);
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		super.setUp();
+		testMessage = getName();
+		Log.getLog(this).addLogListener(logListener);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		super.tearDown();
+		Log.getLog(this).removeLogListener(logListener);
+	}
+
+	public void testCorrectLogIsSelectedForObjectContext() {
+		assertEquals(RuntimeTestsPlugin.getPlugin().getLog(), Log.getLog(this));
+	}
+
+	public void testCorrectLogIsSelectedForClassContext() {
+		assertEquals(RuntimeTestsPlugin.getPlugin().getLog(), Log.getLog(LogTest.class));
+	}
+
+	public void testCorrectLogIsSelectedForStringContext() {
+		assertEquals(RuntimeTestsPlugin.getPlugin().getLog(), Log.getLog(RuntimeTestsPlugin.PI_RUNTIME_TESTS));
+	}
+
+	public void testLogError() {
+		Log.error(this, testMessage);
+		assertLogged(IStatus.ERROR, testMessage, null);
+	}
+
+	public void testLogErrorWithException() {
+		RuntimeException exception = new RuntimeException();
+		Log.error(this, testMessage, exception);
+		assertLogged(IStatus.ERROR, testMessage, exception);
+	}
+
+	public void testLogWarning() {
+		Log.warning(this, testMessage);
+		assertLogged(IStatus.WARNING, testMessage, null);
+	}
+
+	public void testLogWarningWithException() {
+		RuntimeException exception = new RuntimeException();
+		Log.warning(this, testMessage, exception);
+		assertLogged(IStatus.WARNING, testMessage, exception);
+	}
+
+	public void testLogInfo() {
+		Log.info(this, testMessage);
+		assertLogged(IStatus.INFO, testMessage, null);
+	}
+
+	public void testLogInfoWithException() {
+		RuntimeException exception = new RuntimeException();
+		Log.info(this, testMessage, exception);
+		assertLogged(IStatus.INFO, testMessage, exception);
+	}
+
+	public void testLog() {
+		Status status = new Status(IStatus.ERROR, RuntimeTestsPlugin.PI_RUNTIME_TESTS, testMessage);
+		Log.log(this, status);
+		assertLogged(IStatus.ERROR, testMessage, null);
+	}
+
+	private void assertLogged(int error, String message, Throwable exception) {
+		assertEquals("The wrong number of messages were logged", 1, loggedStatus.size());
+		IStatus status = loggedStatus.get(0);
+		assertEquals("The log message had the wrong severity", error, status.getSeverity());
+		assertEquals("The wrong exception was logged", exception, status.getException());
+	}
+
+	public static Test suite() {
+		return new TestSuite(LogTest.class);
+	}
+}