Package mmrnmhrm.core.workspace

Source Code of mmrnmhrm.core.workspace.UpdateAllProjectsBuildpathTask

/*******************************************************************************
* Copyright (c) 2013, 2013 Bruno Medeiros and other Contributors.
* 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:
*     Bruno Medeiros - initial API and implementation
*******************************************************************************/
package mmrnmhrm.core.workspace;

import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;
import static melnorme.utilbox.core.CoreUtil.array;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.Future;

import melnorme.lang.ide.core.LangCore;
import melnorme.lang.ide.core.utils.CoreTaskAgent;
import melnorme.lang.ide.core.utils.DefaultProjectResourceListener;
import melnorme.lang.ide.core.utils.EclipseAsynchJobAdapter;
import melnorme.lang.ide.core.utils.EclipseAsynchJobAdapter.IRunnableWithJob;
import melnorme.lang.ide.core.utils.EclipseUtils;
import melnorme.lang.ide.core.utils.ResourceUtils;
import melnorme.lang.ide.core.utils.process.IRunProcessTask;
import melnorme.utilbox.concurrency.ITaskAgent;
import melnorme.utilbox.misc.ArrayUtil;
import melnorme.utilbox.misc.SimpleLogger;
import melnorme.utilbox.process.ExternalProcessHelper.ExternalProcessResult;
import mmrnmhrm.core.DeeCore;
import mmrnmhrm.core.DeeCoreMessages;
import mmrnmhrm.core.DeeCorePreferences;
import mmrnmhrm.core.engine_client.DubProcessManager;
import mmrnmhrm.core.engine_client.DubProcessManager.DubCompositeOperation;
import mmrnmhrm.core.engine_client.SearchAndAddCompilersTask.SearchAndAddCompilersOnPathJob;
import mmrnmhrm.core.workspace.WorkspaceModelManager.WorkspaceModelManagerTask;

import org.dsource.ddt.ide.core.DeeNature;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.launching.ScriptRuntime;

import dtool.dub.BundlePath;
import dtool.dub.DubBundle;
import dtool.dub.DubBundle.DubBundleException;
import dtool.dub.DubBundleDescription;
import dtool.dub.DubHelper;
import dtool.dub.DubManifestParser;
import dtool.engine.compiler_installs.CompilerInstall;
import dtool.engine.compiler_installs.SearchCompilersOnPathOperation;

/**
* Updates a {@link WorkspaceModel} when resource changes occur, using 'dub describe'.
* Also creates problem markers on the Eclipse workspace.
*/
public class WorkspaceModelManager {
 
  protected static SimpleLogger log = new SimpleLogger(Platform.inDebugMode());
 
  /* ----------------------------------- */
 
  public static final String DUB_PROBLEM_ID = DeeCore.PLUGIN_ID + ".DubProblem";
 
  protected final WorkspaceModel model;
  protected final DubProjectModelResourceListener listener = new DubProjectModelResourceListener();
  protected final ITaskAgent modelAgent = new CoreTaskAgent(getClass().getSimpleName());
  protected final DubProcessManager dubProcessManager = new DubProcessManager();
 
  protected final SearchAndAddCompilersOnPathJob compilerSearchJob = new SearchAndAddCompilersOnPathJob();
 
  protected boolean started = false;

  public WorkspaceModelManager(WorkspaceModel model) {
    this.model = model;
  }
 
  public DubProcessManager getProcessManager() {
    return dubProcessManager;
  }
 
  public SearchAndAddCompilersOnPathJob getCompilersSearchJob() {
    return compilerSearchJob;
  }
 
  public void startManager() {
    log.print("==> Starting: " + getClass().getSimpleName());
    assertTrue(started == false); // start only once
    started = true;
   
    // Run heavyweight initialization in executor thread.
    // This is necessary so that we avoid running the initialization during plugin initialization.
    // Otherwise there could be problems because initialization is heavyweight code:
    // it requests workspace locks (which may not be available) and issues workspace deltas
    modelAgent.submit(new Runnable() {
      @Override
      public void run() {
        initializeModelManager();
      }
    });
  }
 
  public void shutdownManager() {
    // It is possible to shutdown the manager without having it started.
   
    ResourcesPlugin.getWorkspace().removeResourceChangeListener(listener);
    // shutdown model manager agent first, since model agent uses dub process agent
    modelAgent.shutdownNow();
    dubProcessManager.shutdownNow();
    try {
      modelAgent.awaitTermination();
    } catch (InterruptedException e) {
      DeeCore.logInternalError(e);
    }
  }
 
  protected void initializeModelManager() {
    // First of all, search for compilers on the path
    compilerSearchJob.schedule();
    try {
      compilerSearchJob.join();
    } catch (InterruptedException ie) {
      // continue, we should still run rest of initialization
    }
   
    try {
      EclipseUtils.getWorkspace().run(new IWorkspaceRunnable() {
        @Override
        public void run(IProgressMonitor monitor) {
          EclipseUtils.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
          initializeProjectsInfo(monitor);
        }
      }, null);
    } catch (CoreException ce) {
      DeeCore.logStatus(ce);
      // This really should not happen, but still try to recover by registering listener.
      EclipseUtils.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
    }
  }
 
  protected void initializeProjectsInfo(@SuppressWarnings("unused") IProgressMonitor monitor) {
    try {
      IProject[] deeProjects = EclipseUtils.getOpenedProjects(DeeNature.NATURE_ID);
      for (IProject project : deeProjects) {
        if(projectHasDubManifestFile(project)) {
          beginProjectDescribeUpdate(project);
        }
      }
      queueUpdateAllProjectsBuildpath(null);
     
    } catch (CoreException ce) {
      DeeCore.logStatus(ce.getStatus());
    }
  }
 
  protected static final Path DUB_BUNDLE_MANIFEST_FILE = new Path(BundlePath.DUB_MANIFEST_FILENAME);
 
  protected static boolean projectHasDubManifestFile(IProject project) {
    IResource packageFile = project.findMember(DUB_BUNDLE_MANIFEST_FILE);
    if(packageFile != null && packageFile.getType() == IResource.FILE) {
      return true;
    }
    return false;
  }
 
  protected final class DubProjectModelResourceListener extends DefaultProjectResourceListener {
   
    @Override
    protected void processProjectDelta(IResourceDelta projectDelta) {
      IProject project = (IProject) projectDelta.getResource();
     
      DubBundleDescription existingProjectModel = model.getBundleInfo(project);
     
      if(projectDelta.getKind() == IResourceDelta.REMOVED || !DeeNature.isAccessible(project, true)) {
        if(existingProjectModel == null) {
          return; // Nothing to remove, might not have been a DUB model project.
        }
        dubProjectRemoved(project);
        return;
      }
     
      switch(projectDelta.getKind()) {
      case IResourceDelta.ADDED:
        if(projectHasDubManifestFile(project)) {
          dubProjectAdded(project);
        }
        break;
      case IResourceDelta.CHANGED:
        if((projectDelta.getFlags() & IResourceDelta.DESCRIPTION) != 0) {
          // It might be the case that project wasn't a DUB model project before, and now is.
          if(existingProjectModel == null) {
            // Then it's true, project has become DUB model project.
            if(projectHasDubManifestFile(project)) {
              dubProjectAdded(project);
            }
            return;
          }
        }
        IResourceDelta[] resourceDeltas = projectDelta.getAffectedChildren();
        if(resourceDeltas == null)
          break;
        for (IResourceDelta resourceDelta : resourceDeltas) {
          if(resourceDelta.getResource().getType() == IResource.FILE &&
              resourceDelta.getProjectRelativePath().equals(DUB_BUNDLE_MANIFEST_FILE)) {
            if(resourceDelta.getResource().getType() == IResource.FILE) {
              dubManifestFileChanged(project);
            }
          }
        }
        break;
      }
    }
   
  }
 
  protected void dubProjectAdded(IProject project) {
    beginProjectDescribeUpdate(project);
    queueUpdateAllProjectsBuildpath(project);
  }
 
  protected void dubProjectRemoved(IProject project) {
    removeProjectModel(project);
    queueUpdateAllProjectsBuildpath(project);
  }
 
  protected void dubManifestFileChanged(final IProject project) {
    beginProjectDescribeUpdate(project);
    // TODO: bug here, we should recalculate manifest for all files, not just buildpath
    queueUpdateAllProjectsBuildpath(project); // We do this because project might have changed name
  }
 
  protected Future<?> queueUpdateAllProjectsBuildpath(IProject project) {
    // TODO: this could be optimized to prevent duplicate tasks queued
    return modelAgent.submit(new UpdateAllProjectsBuildpathTask(this, project));
  }
 
  protected void beginProjectDescribeUpdate(final IProject project) {
    DubBundleDescription unresolvedDescription = readUnresolvedBundleDescription(project);
    ProjectInfo unresolvedProjectInfo = addProjectInfo(project, unresolvedDescription);
   
    modelAgent.submit(new ProjectModelDubDescribeTask(this, project, unresolvedProjectInfo));
  }
 
  protected DubBundleDescription readUnresolvedBundleDescription(final IProject project) {
    java.nio.file.Path location = project.getLocation().toFile().toPath();
    DubBundle unresolvedBundle = DubManifestParser.parseDubBundleFromLocation(BundlePath.create(location));
   
    return new DubBundleDescription(unresolvedBundle);
  }
 
  /* ----------------------------------- */
 
  protected class SearchCompilersOnPathOperation_Eclipse extends SearchCompilersOnPathOperation {
    @Override
    protected void handleWarning(String message) {
      DeeCore.logWarning(message);
    }
  }
 
  protected final ProjectInfo addProjectInfo(IProject project, DubBundleDescription dubBundleDescription) {
    CompilerInstall compilerInstall = new SearchCompilersOnPathOperation_Eclipse().
        searchForCompilersInDefaultPathEnvVars().getPreferredInstall();
   
    return model.addProjectInfo(project, dubBundleDescription, compilerInstall);
  }
 
  protected final void removeProjectModel(IProject project) {
    /*BUG here: updates to model should only occur in model agent. */
    model.removeProjectInfo(project);
  }
 
  public void syncPendingUpdates() {
    modelAgent.waitForPendingTasks();
  }
 
  /** WARNING: this API is intended to be used for tests only */
  public ITaskAgent internal_getModelAgent() {
    return modelAgent;
  }
 
  public static IMarker[] getDubErrorMarkers(IProject project) throws CoreException {
    return project.findMarkers(DUB_PROBLEM_ID, true, IResource.DEPTH_ONE);
  }
 
  protected abstract class WorkspaceModelManagerTask implements Runnable {
   
    protected final WorkspaceModelManager workspaceModelManager;
   
    public WorkspaceModelManagerTask() {
      this.workspaceModelManager = WorkspaceModelManager.this;
    }
   
    protected WorkspaceModel getModel() {
      return model;
    }
   
    protected void logInternalError(CoreException ce) {
      DeeCore.logInternalError(ce);
    }
   
  }
 
}


class ProjectModelDubDescribeTask extends ProjectUpdateBuildpathTask implements IRunnableWithJob {
 
  protected final IProject project;
  protected final ProjectInfo unresolvedProjectInfo;
  protected final  DubBundleDescription unresolvedDescription;
 
  protected ProjectModelDubDescribeTask(WorkspaceModelManager dubModelManager, IProject project,
      ProjectInfo unresolvedProjectInfo) {
    super(dubModelManager);
    this.project = project;
    this.unresolvedProjectInfo = unresolvedProjectInfo;
    unresolvedDescription = unresolvedProjectInfo.getBundleDesc();
  }
 
  protected DubProcessManager getProcessManager() {
    return workspaceModelManager.dubProcessManager;
  }
 
  @Override
  public void run() {
    try {
      ResourceUtils.getWorkspace().run(new IWorkspaceRunnable() {
       
        @Override
        public void run(IProgressMonitor monitor) throws CoreException {
          if(project.exists() == false) {
            return;
          }
          deleteDubMarkers(project);
         
          if(unresolvedDescription.hasErrors() != false) {
            DubBundleException error = unresolvedDescription.getError();
            setDubErrorMarker(project, error.getMessage(), error.getCause());
            return; // only run dub describe if unresolved description had no errors
          }
         
        }
      }, project, 0, null);
    } catch (CoreException ce) {
      logInternalError(ce);
    }
   
    // only run dub describe if unresolved description had no errors
    if(unresolvedDescription.hasErrors() == false) {
      try {
        EclipseAsynchJobAdapter.runUnderAsynchJob(getNameForJob(), this);
      } catch (InterruptedException e) {
        return;
      }
    }
  }
 
  protected String getNameForJob() {
    return "Running 'dub describe' on project: " + project.getName();
  }
 
  protected void deleteDubMarkers(IProject project) throws CoreException {
    IMarker[] markers = WorkspaceModelManager.getDubErrorMarkers(project);
    for (IMarker marker : markers) {
      marker.delete();
    }
  }
 
  protected void setDubErrorMarker(IProject project, String message, Throwable exception) throws CoreException {
    IMarker dubMarker = project.createMarker(WorkspaceModelManager.DUB_PROBLEM_ID);
    String messageExtra = exception == null ? "" : exception.getMessage();
    dubMarker.setAttribute(IMarker.MESSAGE, message + messageExtra);
    dubMarker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
  }
 
 
  @Override
  public void runUnderEclipseJob(IProgressMonitor monitor) {
    assertNotNull(monitor);
    try {
      resolveProjectOperation(monitor);
    } catch (CoreException ce) {
      try {
        setProjectDubError(project, ce.getMessage(), ce.getCause());
      } catch (CoreException e) {
        logInternalError(ce);
      }
    }
  }
 
  protected Void resolveProjectOperation(IProgressMonitor pm) throws CoreException {
    IPath projectLocation = project.getLocation();
    if(projectLocation == null) {
      return null; // Project no longer exists, or not stored in the local filesystem.
    }
   
    BundlePath bundlePath = BundlePath.create(projectLocation.toFile().toPath());
   
    String dubPath = DeeCorePreferences.getEffectiveDubPath();
   
    DubCompositeOperation resolveProjectOperation = new DubCompositeOperation(
      MessageFormat.format(DeeCoreMessages.RunningDubDescribe, project.getName()), project);
    getProcessManager().notifyOperationStarted(resolveProjectOperation);

//    TODO: when new DUB .22 is released
//    getProcessManager().submitDubCommandAndWait(resolveProjectOperation.newDubProcessTask(
//      project, array(dubPath, "upgrade", "--missing-only"), pm));
   
    IRunProcessTask dubDescribeTask = resolveProjectOperation.newDubProcessTask(
      project, array(dubPath, "describe"), pm);
    ExternalProcessResult processHelper = getProcessManager().submitDubCommandAndWait(dubDescribeTask);
   
    int exitValue = processHelper.exitValue;
    if(exitValue != 0) {
      throw LangCore.createCoreException("dub returned non-zero status: " + exitValue, null);
    }
   
    final DubBundleDescription bundleDesc = DubHelper.parseDubDescribe(bundlePath, processHelper);
   
    EclipseUtils.getWorkspace().run(new IWorkspaceRunnable() {
      @Override
      public void run(IProgressMonitor monitor) throws CoreException {
        if(project.exists() == false) {
          return;
        }
       
        if(bundleDesc.hasErrors()) {
          setProjectDubError(project, "Error parsing description:", bundleDesc.getError());
        } else {
          workspaceModelManager.addProjectInfo(project, bundleDesc);
          updateBuildpath(project, bundleDesc);
        }
      }
    }, null, 0, pm);
   
    return null;
  }
 
  protected void setProjectDubError(IProject project, String message, Throwable exception) throws CoreException {
   
    DubBundleException dubError = new DubBundleException(message, exception);
   
    DubBundle main = unresolvedDescription.getMainBundle();
    DubBundleDescription bundleDesc = new DubBundleDescription(main, dubError);
    workspaceModelManager.model.addProjectInfo(project, bundleDesc, unresolvedProjectInfo.compilerInstall);
   
    setDubErrorMarker(project, message, exception);
  }
 
}

abstract class ProjectUpdateBuildpathTask extends WorkspaceModelManagerTask {
 
  protected ProjectUpdateBuildpathTask(WorkspaceModelManager dubModelManager) {
    dubModelManager.super();
  }
 
  protected void updateBuildpath(IProject project, DubBundleDescription bundleDesc) {
    IScriptProject projectElement = DLTKCore.create(project);
   
    ArrayList<IBuildpathEntry> entries = new ArrayList<>();
   
    entries.add(DLTKCore.newContainerEntry(ScriptRuntime.newDefaultInterpreterContainerPath()));
    entries.add(DLTKCore.newContainerEntry(new Path(DubDependenciesBuildpathContainer.CONTAINER_PATH_ID)));
   
    for (java.nio.file.Path srcFolder : bundleDesc.getMainBundle().getEffectiveSourceFolders()) {
      IPath path2 = projectElement.getPath().append(srcFolder.toString());
      entries.add(DLTKCore.newSourceEntry(path2));
    }
   
    try {
      IBuildpathEntry[] bpEntriesFromDeps = getBuildpathEntriesFromDeps(bundleDesc);
      updateDubBuildpathContainer(projectElement, bpEntriesFromDeps);
      projectElement.setRawBuildpath(ArrayUtil.createFrom(entries, IBuildpathEntry.class), null);
    } catch (ModelException me) {
      logInternalError(me);
    }
  }
 
  protected void updateDubBuildpathContainer(IScriptProject projectElement, IBuildpathEntry[] entries)
      throws ModelException {
    DubDependenciesBuildpathContainer dubContainer = new DubDependenciesBuildpathContainer(projectElement, entries);
    DLTKCore.setBuildpathContainer(DubDependenciesBuildpathContainer.CONTAINER_PATH, array(projectElement),
      array(dubContainer), null);
  }
 
  protected IBuildpathEntry[] getBuildpathEntriesFromDeps(DubBundleDescription bundleDesc) {
    ArrayList<IBuildpathEntry> depEntries = new ArrayList<>();
    for (DubBundle depBundle : bundleDesc.getBundleDependencies()) {
      BundlePath bundlePath = depBundle.getBundlePath();
      if(depBundle.hasErrors() || bundlePath == null) {
        continue;
      }
      IProject workspaceProject = findProjectForBundle(depBundle);
      if(workspaceProject != null) {
        depEntries.add(DLTKCore.newProjectEntry(workspaceProject.getFullPath(), true));
      } else {
        for (java.nio.file.Path srcFolder : depBundle.getEffectiveSourceFolders()) {
         
          java.nio.file.Path srcFolderAbsolute = bundlePath.resolve(srcFolder);
          assertTrue(srcFolderAbsolute.isAbsolute());
          depEntries.add(
            DubDependenciesBuildpathContainer.createDubBuildpathEntry(EclipseUtils.path(srcFolderAbsolute)));
        }
      }
    }
   
    return ArrayUtil.createFrom(depEntries, IBuildpathEntry.class);
  }
 
  protected IProject findProjectForBundle(DubBundle depBundle) {
    return findProjectForBundleLocation(depBundle.getLocation());
  }
 
  protected IProject findProjectForBundleLocation(java.nio.file.Path location) {
    if(location == null) {
      return null;
    }
   
    // BM: There is a minor race condition here that will cause a temporary inconsistent state.
    // TODO: to avoid that we should maintain a mapping of location->project in Dub Model.
    IProject[] deeProjects;
    try {
      deeProjects = EclipseUtils.getOpenedProjects(DeeCore.NATURE_ID);
    } catch (CoreException e) {
      return null;
    }
    for (IProject project : deeProjects) {
      if(project.getLocation().toFile().toPath().equals(location) && getModel().getBundleInfo(project) != null) {
        return project;
      }
    }
   
    return null;
  }
}

class UpdateAllProjectsBuildpathTask extends ProjectUpdateBuildpathTask {
 
  protected IProject changedProject;

  protected UpdateAllProjectsBuildpathTask(WorkspaceModelManager modelManager, IProject changedProject) {
    super(modelManager);
    this.changedProject = changedProject;
  }
 
  @Override
  public void run() {
    Set<String> dubProjects = getModel().getDubProjects();
    for (String projectName : dubProjects) {
      if(changedProject != null && changedProject.getName().equals(projectName))
        continue; // changedProject is supposed to be up to date, so no need to update that one.
     
      IProject project = EclipseUtils.getWorkspaceRoot().getProject(projectName);
      ProjectInfo projectInfo = getModel().getProjectInfo(project);
      // Check if project info exists, the project, might have been removed in the meanwhile.
      if(projectInfo != null) {
        /*BUG here project exists. */
        updateBuildpath(project, projectInfo.getBundleDesc());
      }
    }
  }
 
}
TOP

Related Classes of mmrnmhrm.core.workspace.UpdateAllProjectsBuildpathTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.