package sk.stuba.fiit.perconik.core.plugin;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.base.Stopwatch;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IStartup;
import org.eclipse.ui.IWorkbench;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import sk.stuba.fiit.perconik.core.ListenerRegistrationException;
import sk.stuba.fiit.perconik.core.Listeners;
import sk.stuba.fiit.perconik.core.ResourceRegistrationException;
import sk.stuba.fiit.perconik.core.listeners.WorkbenchListener;
import sk.stuba.fiit.perconik.eclipse.core.runtime.ExtendedPlugin;
import sk.stuba.fiit.perconik.eclipse.core.runtime.PluginConsole;
import sk.stuba.fiit.perconik.eclipse.swt.widgets.DisplayExecutor;
import sk.stuba.fiit.perconik.osgi.framework.BundleNotFoundException;
import sk.stuba.fiit.perconik.osgi.framework.Bundles;
import sk.stuba.fiit.perconik.utilities.concurrent.TimeValue;
import sk.stuba.fiit.perconik.utilities.reflect.resolver.ClassResolver;
import sk.stuba.fiit.perconik.utilities.reflect.resolver.ClassResolvers;
import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.util.concurrent.Runnables.doNothing;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static org.eclipse.jface.dialogs.MessageDialog.openError;
import static sk.stuba.fiit.perconik.eclipse.ui.Workbenches.waitForWorkbench;
/**
* The <code>Activator</code> class controls the plug-in life cycle.
*
* <p><b>Warning:</b> Users should not explicitly instantiate this class.
*
* @author Pavol Zbell
*/
public final class Activator extends ExtendedPlugin {
/**
* The plug-in identifier.
*/
public static final String PLUGIN_ID = "sk.stuba.fiit.perconik.core";
/**
* The shared instance.
*/
private static volatile Activator plugin;
/**
* Indicates whether core plug-in extensions are processed or not.
*/
private volatile boolean loaded;
/**
* The constructor.
*/
public Activator() {}
/**
* Gets the shared instance.
* @return the shared instance or {@code null}
*/
public static Activator defaultInstance() {
return plugin;
}
/**
* Gets the shared console.
* @return the shared console or {@code null}
*/
public static PluginConsole defaultConsole() {
Activator plugin = defaultInstance();
return plugin != null ? plugin.getConsole() : null;
}
/**
* Returns a set of extension contributors to this plug-in.
*/
public static Set<String> extensionContributors() {
Set<String> contributors = newHashSet();
for (String point: ExtensionPoints.all) {
for (IConfigurationElement element: Platform.getExtensionRegistry().getConfigurationElementsFor(point)) {
contributors.add(element.getContributor().getName());
}
}
return contributors;
}
/**
* Returns a list of bundles contributing extensions to this plug-in.
*/
public static List<Bundle> contributingBundles() {
try {
return Bundles.forNames(extensionContributors());
} catch (BundleNotFoundException e) {
throw propagate(e);
}
}
/**
* Returns primary {@link ClassResolver} utility to resolve unknown classes.
*/
public static ClassResolver classResolver() {
List<ClassResolver> resolvers = newArrayList();
resolvers.add(Bundles.newClassResolver(defaultInstance().getBundle()));
resolvers.addAll(Bundles.newClassResolvers(contributingBundles()));
return ClassResolvers.compose(resolvers);
}
/**
* Processes supplied extensions, loads and starts core services.
* @param action executed after services load prior to start, not {@code null}
* @param timeout the maximum time to load
* @param unit the time unit of the timeout argument
* @throws RuntimeException if an error occurred
* @throws TimeoutException if the load timed out
*/
public static void loadServices(final Runnable hook, final long timeout, final TimeUnit unit) throws TimeoutException {
Activator plugin = defaultInstance();
checkNotNull(plugin, "Default instance not available");
synchronized (plugin) {
if (plugin.loaded) {
throw new IllegalStateException("Core services already loaded");
}
try {
new ServicesLoader().load(hook, timeout, unit);
plugin.loaded = true;
} catch (TimeoutException failure) {
throw failure;
} catch (Throwable failure) {
propagate(failure);
}
}
}
public static void loadServices(final Runnable hook, final TimeValue timeout) throws TimeoutException {
loadServices(hook, timeout.duration(), timeout.unit());
}
/**
* Processes supplied extensions, stops and unloads core services.
* @param action executed after services stop prior to unload, not {@code null}
* @param timeout the maximum time to unload
* @param unit the time unit of the timeout argument
* @throws RuntimeException if an error occurred
* @throws TimeoutException if the unload timed out
*/
public static void unloadServices(final Runnable hook, final long timeout, final TimeUnit unit) throws TimeoutException {
Activator plugin = defaultInstance();
checkNotNull(plugin, "Default instance not available");
synchronized (plugin) {
if (!plugin.loaded) {
throw new IllegalStateException("Core services not loaded yet");
}
try {
new ServicesLoader().unload(hook, timeout, unit);
plugin.loaded = false;
} catch (TimeoutException failure) {
throw failure;
} catch (Throwable failure) {
propagate(failure);
}
}
}
public static void unloadServices(final Runnable hook, final TimeValue timeout) throws TimeoutException {
unloadServices(hook, timeout.duration(), timeout.unit());
}
/**
* Determines whether all supplied extensions
* are processed, core services loaded and started.
*/
public static boolean loadedServices() {
Activator plugin = defaultInstance();
return plugin != null && plugin.loaded;
}
/**
* Waits blocking until all supplied extensions
* are processed, core services loaded and started.
* @param timeout the maximum time to wait
* @param unit the time unit of the timeout argument
* @throws TimeoutException if the wait timed out
*/
public static void awaitServices(final long timeout, final TimeUnit unit) throws TimeoutException {
Stopwatch stopwatch = Stopwatch.createStarted();
while (!loadedServices()) {
if (stopwatch.elapsed(unit) > timeout) {
throw new TimeoutException();
}
sleepUninterruptibly(20, MILLISECONDS);
}
}
public static void awaitServices(final TimeValue timeout) throws TimeoutException {
awaitServices(timeout.duration(), timeout.unit());
}
/**
* Plug-in early startup.
*
* <p><b>Warning:</b> Users should not explicitly instantiate this class.
*
* @author Pavol Zbell
* @since 1.0
*/
public static final class Startup implements IStartup {
static final TimeValue timeout = TimeValue.of(8, SECONDS);
/**
* The constructor.
*/
public Startup() {}
/**
* Processes supplied extensions and starts core services.
*/
public void earlyStartup() {
try {
loadServices(doNothing(), timeout);
} catch (ResourceRegistrationException failure) {
reportFailure(failure, "Unexpected error during initial registration of resources");
} catch (ListenerRegistrationException failure) {
reportFailure(failure, "Unexpected error during initial registration of listeners");
} catch (TimeoutException failure) {
reportFailure(failure, "Unexpected timeout while loading services");
} catch (Throwable failure) {
reportFailure(failure, "Unexpected error while loading services");
}
try {
dispatchPostStartup();
} catch (Exception failure) {
reportFailure(failure, "Unexpected error during post startup event dispatch");
}
}
private static void dispatchPostStartup() {
DisplayExecutor.defaultAsynchronous().execute(new Runnable() {
public void run() {
List<Throwable> failures = newLinkedList();
IWorkbench workbench = waitForWorkbench();
for (WorkbenchListener listener: Listeners.registered(WorkbenchListener.class)) {
try {
listener.postStartup(workbench);
} catch (Throwable failure) {
failures.add(failure);
defaultConsole().error(failure, "Unexpected error during post startup event dispatch on %s", listener);
}
}
checkState(failures.isEmpty());
}
});
}
private static void reportFailure(final Throwable failure, final String description) {
DisplayExecutor.defaultAsynchronous().execute(new Runnable() {
public void run() {
String title = "PerConIK Core";
String message = format("%s, core plug-in may not be properly active.", description);
openError(Display.getDefault().getActiveShell(), title, message + " See error log for more details.");
}
});
defaultInstance().getConsole().error(failure, description);
}
}
/**
* Starts this plug-in.
*
* <p><b>Warning:</b> Users must never explicitly call this method.
*/
@Override
public void start(final BundleContext context) throws Exception {
this.loaded = false;
super.start(context);
plugin = this;
}
/**
* Stops this plug-in.
*
* <p><b>Warning:</b> Users must never explicitly call this method.
*/
@Override
public void stop(final BundleContext context) throws Exception {
final TimeValue timeout = TimeValue.of(12, SECONDS);
synchronized (this) {
if (loadedServices()) {
try {
unloadServices(doNothing(), timeout);
} catch (TimeoutException failure) {
defaultConsole().error(failure, "Unexpected timeout while unloading services");
}
}
}
plugin = null;
super.stop(context);
this.loaded = false;
}
}