package bndtools.central;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import org.bndtools.api.BndtoolsConstants;
import org.bndtools.api.ILogger;
import org.bndtools.api.IStartupParticipant;
import org.bndtools.api.Logger;
import org.bndtools.api.ModelListener;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.viewers.TreeViewer;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Failure;
import org.osgi.util.promise.Promise;
import org.osgi.util.promise.Success;
import aQute.bnd.build.Project;
import aQute.bnd.build.Workspace;
import aQute.bnd.header.Attrs;
import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Processor;
import aQute.bnd.service.Refreshable;
import aQute.bnd.service.RepositoryPlugin;
import bndtools.central.RepositoriesViewRefresher.RefreshModel;
import bndtools.preferences.BndPreferences;
public class Central implements IStartupParticipant {
private static final ILogger logger = Logger.getLogger(Central.class);
private static volatile Central instance = null;
private static volatile Workspace workspace = null;
private static final Deferred<Workspace> workspaceQueue = new Deferred<>();
static WorkspaceR5Repository r5Repository = null;
private static Auxiliary auxiliary;
static final AtomicBoolean indexValid = new AtomicBoolean(false);
private final BundleContext bundleContext;
private final Map<IJavaProject,Project> javaProjectToModel = new HashMap<IJavaProject,Project>();
private final List<ModelListener> listeners = new CopyOnWriteArrayList<ModelListener>();
private RepositoryListenerPluginTracker repoListenerTracker;
@SuppressWarnings("unused")
private static WorkspaceRepositoryChangeDetector workspaceRepositoryChangeDetector;
private static RepositoriesViewRefresher repositoriesViewRefresher = new RepositoriesViewRefresher();
static {
try {
BundleContext context = FrameworkUtil.getBundle(Central.class).getBundleContext();
Bundle bndlib = FrameworkUtil.getBundle(Workspace.class);
auxiliary = new Auxiliary(context, bndlib);
} catch (Exception e) {
// ignore
}
}
/**
* WARNING: Do not instantiate this class. It must be public to allow instantiation by the Eclipse registry, but it
* is not intended for direct creation by clients. Instead call Central.getInstance().
*/
@Deprecated
public Central() {
bundleContext = FrameworkUtil.getBundle(Central.class).getBundleContext();
}
@Override
public void start() {
instance = this;
repoListenerTracker = new RepositoryListenerPluginTracker(bundleContext);
repoListenerTracker.open();
}
@Override
public void stop() {
repoListenerTracker.close();
instance = null;
Workspace ws = workspace;
if (ws != null) {
ws.close();
}
if (auxiliary != null)
try {
auxiliary.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Central getInstance() {
return instance;
}
public Project getModel(IJavaProject project) {
try {
Project model = javaProjectToModel.get(project);
if (model == null) {
try {
model = getProject(project.getProject());
} catch (IllegalArgumentException e) {
// initialiseWorkspace();
// model = Central.getProject(projectDir);
return null;
}
if (workspace == null) {
model.getWorkspace();
}
if (model != null) {
javaProjectToModel.put(project, model);
}
}
return model;
} catch (Exception e) {
// TODO do something more useful here
throw new RuntimeException(e);
}
}
public static IFile getWorkspaceBuildFile() throws Exception {
File file = Central.getWorkspace().getPropertiesFile();
IFile[] matches = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(file.toURI());
if (matches == null || matches.length != 1) {
logger.logError("Cannot find workspace location for bnd configuration file " + file, null);
return null;
}
return matches[0];
}
public synchronized static WorkspaceR5Repository getWorkspaceR5Repository() throws Exception {
if (r5Repository != null)
return r5Repository;
r5Repository = new WorkspaceR5Repository();
r5Repository.init();
return r5Repository;
}
public synchronized static RepositoryPlugin getWorkspaceRepository() throws Exception {
return getWorkspace().getWorkspaceRepository();
}
public static Workspace getWorkspaceIfPresent() {
try {
return getWorkspace();
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) {
return null;
}
}
public static Workspace getWorkspace() throws Exception {
if (getInstance() == null) {
throw new IllegalStateException("Central is not initialised");
}
Workspace ws;
boolean resolve;
synchronized (workspaceQueue) {
ws = workspace;
File workspaceDirectory = getWorkspaceDirectory();
if (ws != null) {
if (workspaceDirectory != null && ws.isDefaultWorkspace()) {
ws.setFileSystem(workspaceDirectory, Workspace.CNFDIR);
ws.refresh();
resolve = !workspaceQueue.getPromise().isDone();
} else if (workspaceDirectory == null && !ws.isDefaultWorkspace()) {
ws.setFileSystem(Workspace.BND_DEFAULT_WS, Workspace.CNFDIR);
ws.refresh();
resolve = false;
} else {
resolve = false;
}
} else {
try {
Workspace.setDriver(Constants.BNDDRIVER_ECLIPSE);
Workspace.addGestalt(Constants.GESTALT_INTERACTIVE, new Attrs());
if (workspaceDirectory == null) {
// there is no cnf project. So
// we create a temp workspace
ws = Workspace.createDefaultWorkspace();
resolve = false;
} else {
ws = Workspace.getWorkspace(workspaceDirectory);
resolve = true;
}
ws.setOffline(new BndPreferences().isWorkspaceOffline());
ws.addBasicPlugin(new WorkspaceListener(ws));
ws.addBasicPlugin(getInstance().repoListenerTracker);
ws.addBasicPlugin(getWorkspaceR5Repository());
ws.addBasicPlugin(new JobProgress());
// Initialize projects in synchronized block
ws.getBuildOrder();
// Monitor changes in cnf so we can refresh the workspace
addCnfChangeListener(ws);
workspaceRepositoryChangeDetector = new WorkspaceRepositoryChangeDetector(ws);
// The workspace has been initialized fully, set the field now
workspace = ws;
} catch (final Exception e) {
if (ws != null) {
ws.close();
}
throw e;
}
}
}
if (resolve)
workspaceQueue.resolve(ws); // notify onWorkspaceInit callbacks
return ws;
}
public static void onWorkspaceInit(final Success<Workspace,Void> callback) {
Promise<Workspace> p = workspaceQueue.getPromise();
p.then(callback, null).then(null, callbackFailure);
}
public static boolean isWorkspaceInited() {
return workspace != null;
}
private static final Failure callbackFailure = new Failure() {
@Override
public void fail(Promise< ? > resolved) throws Exception {
logger.logError("onWorkspaceInit callback failed", resolved.getFailure());
}
};
private static File getWorkspaceDirectory() throws CoreException {
IWorkspaceRoot eclipseWorkspace = ResourcesPlugin.getWorkspace().getRoot();
IProject cnfProject = eclipseWorkspace.getProject(Workspace.BNDDIR);
if (!cnfProject.exists())
cnfProject = eclipseWorkspace.getProject(Workspace.CNFDIR);
if (cnfProject.exists()) {
if (!cnfProject.isOpen())
cnfProject.open(null);
return cnfProject.getLocation().toFile().getParentFile();
}
return null;
}
public static boolean hasWorkspaceDirectory() {
try {
return getWorkspaceDirectory() != null;
} catch (CoreException e) {
return false;
}
}
private static void addCnfChangeListener(final Workspace workspace) {
ResourcesPlugin.getWorkspace().addResourceChangeListener(new IResourceChangeListener() {
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (Central.getInstance() == null) { // plugin is not active
return;
}
if (event.getType() != IResourceChangeEvent.POST_CHANGE) {
return;
}
IResourceDelta rootDelta = event.getDelta();
if (isCnfChanged(workspace, rootDelta)) {
logger.logInfo("cnf changed; refreshing workspace", null);
workspace.refresh();
}
}
});
}
private static boolean isCnfChanged(Workspace workspace, IResourceDelta rootDelta) {
try {
IPath path = toPath(workspace.getPropertiesFile());
if (path != null && rootDelta.findMember(path) != null) {
return true;
}
List<File> includedFiles = workspace.getIncluded();
if (includedFiles != null) {
for (File includedFile : includedFiles) {
path = toPath(includedFile);
if (path != null && rootDelta.findMember(path) != null) {
return true;
}
}
}
} catch (Exception e) {
logger.logError("Central.isCnfChanged() failed", e);
}
return false;
}
public static boolean isChangeDelta(IResourceDelta delta) {
if (IResourceDelta.MARKERS == delta.getFlags())
return false;
if ((delta.getKind() & (IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED)) == 0)
return false;
return true;
}
public void changed(Project model) {
model.setChanged();
for (ModelListener m : listeners)
try {
m.modelChanged(model);
} catch (Exception e) {
e.printStackTrace();
}
}
public void addModelListener(ModelListener m) {
if (!listeners.contains(m)) {
listeners.add(m);
}
}
public void removeModelListener(ModelListener m) {
listeners.remove(m);
}
public static IJavaProject getJavaProject(Project model) {
for (IProject iproj : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (iproj.getName().equals(model.getName())) {
IJavaProject ij = JavaCore.create(iproj);
if (ij != null && ij.exists()) {
return ij;
}
break; // current project is not a Java project
}
}
return null;
}
public static IPath toPath(File file) throws Exception {
IPath result = null;
File absolute = file.getCanonicalFile();
IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
IFile[] candidates = wsroot.findFilesForLocationURI(absolute.toURI());
if (candidates != null && candidates.length > 0) {
result = candidates[0].getFullPath();
} else {
String workspacePath = getWorkspace().getBase().getAbsolutePath();
String absolutePath = absolute.getPath();
if (absolutePath.startsWith(workspacePath))
result = new Path(absolutePath.substring(workspacePath.length()));
}
return result;
}
public static IPath toPathMustBeInEclipseWorkspace(File file) throws Exception {
IPath result = null;
File absolute = file.getCanonicalFile();
IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
IFile[] candidates = wsroot.findFilesForLocationURI(absolute.toURI());
if (candidates != null && candidates.length > 0) {
result = candidates[0].getFullPath();
}
return result;
}
public static void refresh(IPath path) {
try {
IResource r = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (r != null)
return;
IPath p = (IPath) path.clone();
while (p.segmentCount() > 0) {
p = p.removeLastSegments(1);
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(p);
if (resource != null) {
resource.refreshLocal(2, null);
return;
}
}
} catch (Exception e) {
logger.logError("While refreshing path " + path, e);
}
}
public static void refreshPlugins() throws Exception {
List<File> refreshedFiles = new ArrayList<File>();
List<Refreshable> rps = getWorkspace().getPlugins(Refreshable.class);
boolean changed = false;
for (Refreshable rp : rps) {
if (rp.refresh()) {
changed = true;
File root = rp.getRoot();
if (root != null)
refreshedFiles.add(root);
}
}
//
// If repos were refreshed then
// we should also update the classpath
// containers. We can force this by setting the "bndtools.refresh" property.
//
if (changed) {
try {
for (File file : refreshedFiles) {
refreshFile(file);
}
for (Project p : Central.getWorkspace().getAllProjects()) {
p.setChanged();
for (ModelListener l : getInstance().listeners)
l.modelChanged(p);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public static void refreshPlugin(Refreshable plugin) throws Exception {
if (plugin.refresh()) {
refreshFile(plugin.getRoot());
for (Project p : Central.getWorkspace().getAllProjects()) {
p.setChanged();
for (ModelListener l : getInstance().listeners)
l.modelChanged(p);
}
}
}
public static void refreshFile(File f) throws Exception {
String path = toLocal(f);
IResource r = ResourcesPlugin.getWorkspace().getRoot().findMember(path);
if (r != null) {
r.refreshLocal(IResource.DEPTH_INFINITE, null);
}
}
public static void refresh(Project p) throws Exception {
IJavaProject jp = getJavaProject(p);
if (jp != null)
jp.getProject().refreshLocal(IResource.DEPTH_INFINITE, null);
}
private static String toLocal(File f) throws Exception {
String root = getWorkspace().getBase().getAbsolutePath();
String path = f.getAbsolutePath();
if (path.startsWith(root))
return f.getAbsolutePath().substring(root.length());
return path;
}
public void close() {
repositoriesViewRefresher.close();
}
public static void invalidateIndex() {
indexValid.set(false);
}
public static boolean needsIndexing() {
return indexValid.compareAndSet(false, true);
}
public static Project getProject(File projectDir) throws Exception {
return getWorkspace().getProjectFromFile(projectDir);
}
public static Project getProject(IProject p) throws Exception {
return getProject(p.getLocation().toFile());
}
/**
* Return the IResource associated with a file
*
* @param file
* @return
*/
public static IResource toResource(File file) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile[] ifiles = root.findFilesForLocationURI(file.toURI());
if (ifiles == null || ifiles.length == 0)
return null;
return ifiles[0];
}
/**
* Reentrant lock for serializing access to bnd code.
*/
private static final ReentrantLock bndLock = new ReentrantLock();
/**
* Used to serialize access to bnd code which is not thread safe.
*
* @param callable
* The code to execute while holding the central lock.
* @return The result of the specified callable.
* @throws InterruptedException
* If the thread is interrupted while waiting for the lock.
* @throws TimeoutException
* If the lock was not obtained within the timeout period.
* @throws Exception
* If the callable throws an exception.
*/
public static <V> V bndCall(Callable<V> callable) throws Exception {
return bndCall(callable, new NullProgressMonitor());
}
/**
* Used to serialize access to bnd code which is not thread safe.
*
* @param callable
* The code to execute while holding the central lock.
* @param monitor
* If the monitor is cancelled, a TimeoutException will be thrown.
* @return The result of the specified callable.
* @throws InterruptedException
* If the thread is interrupted while waiting for the lock.
* @throws TimeoutException
* If the lock was not obtained within the timeout period or the specified monitor is cancelled while
* waiting to obtain the lock.
* @throws Exception
* If the callable throws an exception.
*/
public static <V> V bndCall(Callable<V> callable, IProgressMonitor monitor) throws Exception {
boolean interrupted = Thread.interrupted();
try {
boolean locked = false;
for (int i = 0; !locked && (i < 60) && !monitor.isCanceled(); i++) {
try {
locked = bndLock.tryLock(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
interrupted = true;
throw e;
}
}
if (!locked) {
throw new TimeoutException("Unable to acquire bndLock");
}
try {
return callable.call();
} finally {
bndLock.unlock();
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/**
* Convert a processor to a status object
*/
public static IStatus toStatus(Processor processor, String message) {
int severity = IStatus.INFO;
List<IStatus> statuses = new ArrayList<IStatus>();
for (String error : processor.getErrors()) {
Status status = new Status(IStatus.ERROR, BndtoolsConstants.CORE_PLUGIN_ID, error);
statuses.add(status);
severity = IStatus.ERROR;
}
for (String warning : processor.getWarnings()) {
Status status = new Status(IStatus.WARNING, BndtoolsConstants.CORE_PLUGIN_ID, warning);
statuses.add(status);
severity = IStatus.WARNING;
}
IStatus[] array = statuses.toArray(new IStatus[0]);
return new MultiStatus(//
BndtoolsConstants.CORE_PLUGIN_ID, //
severity, //
array, message, null);
}
/**
* Register a viewer with repositories
*/
public static void addRepositoriesViewer(TreeViewer viewer, RepositoriesViewRefresher.RefreshModel model) {
repositoriesViewRefresher.addViewer(viewer, model);
}
/**
* Unregister a viewer with repositories
*/
public static void removeRepositoriesViewer(TreeViewer viewer) {
repositoriesViewRefresher.removeViewer(viewer);
}
public static void setRepositories(TreeViewer viewer, RefreshModel model) {
repositoriesViewRefresher.setRepositories(viewer, model);
}
}