/** * Copyright (c) 2012 by JP Moresmau * This code is made available under the terms of the Eclipse Public License, * version 1.0 (EPL). See http://www.eclipse.org/legal/epl-v10.html */ package net.sf.eclipsefp.haskell.buildwrapper; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import net.sf.eclipsefp.haskell.buildwrapper.types.BuildOptions; import net.sf.eclipsefp.haskell.buildwrapper.types.EvalHandler; import net.sf.eclipsefp.haskell.buildwrapper.types.EvalResult; import net.sf.eclipsefp.haskell.buildwrapper.types.ImportCleanHandler; import net.sf.eclipsefp.haskell.buildwrapper.types.Location; import net.sf.eclipsefp.haskell.buildwrapper.types.NameDef; import net.sf.eclipsefp.haskell.buildwrapper.types.NameDefHandler; import net.sf.eclipsefp.haskell.buildwrapper.types.OccurrencesHandler; import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineHandler; import net.sf.eclipsefp.haskell.buildwrapper.types.OutlineResult; import net.sf.eclipsefp.haskell.buildwrapper.types.ThingAtPointHandler; import net.sf.eclipsefp.haskell.buildwrapper.usage.UsageThread; import net.sf.eclipsefp.haskell.buildwrapper.util.BWText; import net.sf.eclipsefp.haskell.util.SingleJobQueue; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.osgi.util.NLS; import org.json.JSONArray; /** * performs operations in a job * @author JP Moresmau * */ public class JobFacade { private BWFacade realFacade; public JobFacade(BWFacade realF){ realFacade=realF; } public static class BuildJob extends Job { private JSONArray notes; private BuildOptions buildOptions; private BWFacade realFacade; public BuildJob(String name,BWFacade realFacade,BuildOptions buildOptions) { super(name); this.realFacade=realFacade; this.buildOptions=buildOptions; } @Override protected IStatus run(IProgressMonitor monitor) { try { notes=realFacade.build(buildOptions); if (buildOptions.isOutput()){ new Thread(new Runnable(){ @Override public void run() { try { IProgressMonitor monitor=new NullProgressMonitor(); IResource res=realFacade.getProject().findMember(BWFacade.DIST_FOLDER); if (res!=null){ res.refreshLocal(IResource.DEPTH_INFINITE, monitor); } else { realFacade.getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor); } } catch (CoreException ce){ BuildWrapperPlugin.logError(BWText.error_refreshLocal, ce); ce.printStackTrace(); } }}).start(); } // do all that as build job otherwise launch start too early boolean ok=realFacade.parseBuildResult(notes); if (ok){ realFacade.closeAllProcesses(); // GHC needs to reload the changes. It can do it for source files, but not across components String name=NLS.bind(BWText.job_sandbox_deps, realFacade.getProject().getName()); Job installJob=new Job(name) { @Override protected IStatus run(IProgressMonitor arg0) { try { SandboxHelper.updateUsing(realFacade); } catch (CoreException ce){ BuildWrapperPlugin.logError(BWText.error_sandbox,ce); } return Status.OK_STATUS; } }; installJob.setPriority(Job.BUILD); installJob.schedule(); } } finally { monitor.done(); } return Status.OK_STATUS; } public JSONArray getNotes() { return notes; } } public BuildJob getBuildJob(final BuildOptions buildOptions){ final String jobNamePrefix = NLS.bind(BWText.job_build, getProject().getName()); final BuildJob buildJob=new BuildJob(jobNamePrefix,realFacade,buildOptions); // buildJob.addJobChangeListener(new JobChangeAdapter(){ // @Override // public void done(IJobChangeEvent event) { // if (event.getResult().isOK()){ // Job parseJob = new Job (jobNamePrefix) { // protected IStatus run(IProgressMonitor arg0) { // boolean ok=realFacade.parseBuildResult(buildJob.getNotes()); // if (ok){ // realFacade.closeAllProcesses(); // GHC needs to reload the changes. It can do it for source files, but not across components // String name=NLS.bind(BWText.job_sandbox_deps, realFacade.getProject().getName()); // Job installJob=new Job(name) { // // @Override // protected IStatus run(IProgressMonitor arg0) { // try { // SandboxHelper.updateUsing(realFacade); // } catch (CoreException ce){ // BuildWrapperPlugin.logError(BWText.error_sandbox,ce); // } // return Status.OK_STATUS; // } // }; // installJob.setPriority(Job.BUILD); // installJob.schedule(); // } // return Status.OK_STATUS; // }; // }; // parseJob.setRule( getProject() ); // parseJob.setPriority(Job.BUILD); // parseJob.schedule(); // } // } // }); // not needed since we put the job in a queue ourselves // except if both synchronized and build are run concurrently //buildJob.setRule( getProject() ); buildJob.setPriority(Job.BUILD); return buildJob; } public void build(final BuildOptions buildOptions) { realFacade.getBuildJobQueue().addJob(getBuildJob(buildOptions)); } /** * build without starting a new job * @param buildOptions * @param monitor */ public void build(final BuildOptions buildOptions,final IProgressMonitor monitor) { BuildJob bj=getBuildJob(buildOptions); bj.run(monitor); } public void synchronizeAndBuild(final boolean force,final BuildOptions buildOptions,final IProgressMonitor monitor){ /*Job sj=getSynchronizeJob(force,false); /*sj.addJobChangeListener(new JobChangeAdapter(){ @Override public void done(IJobChangeEvent event) { if (event.getResult().isOK()){ BuildJob j=getBuildJob(buildOptions); addUsage(j); realFacade.getBuildJobQueue().addJob(j); } } }); sj.schedule();*/ realFacade.synchronize(force); if (monitor!=null && monitor.isCanceled()){ return; } BuildJob bj=getBuildJob(buildOptions); IStatus st=bj.run(monitor); if (st.isOK()){ UsageThread ut=BuildWrapperPlugin.getDefault().getUsageThread(); if (ut!=null){ ut.addProject(getProject()); } } } /** * @return the realFacade */ public BWFacade getRealFacade() { return realFacade; } /** * a simple rule only conflicting with itself */ private static final ISchedulingRule synchronizeRule=new ISchedulingRule() { @Override public boolean isConflicting(ISchedulingRule arg0) { return this==arg0; } @Override public boolean contains(ISchedulingRule arg0) { return this==arg0; } }; private Job getSynchronizeJob(final boolean force, final boolean usage) { final String jobNamePrefix = NLS.bind(BWText.job_synchronize, getProject().getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); realFacade.synchronize(force); } finally { monitor.done(); } return Status.OK_STATUS; } }; // why? // build is a project only rule, but synchronize is hard on the disk, so we only want one synchronize at the same time // but we don't want buildJob.setRule( getProject().getWorkspace().getRoot() ); because then any other operation will wait for synchronize // so we only disallow concurrent synchronize, but don't influence with the rest (other operations) buildJob.setRule(synchronizeRule); buildJob.setPriority(Job.BUILD); if (usage){ addUsage(buildJob); } return buildJob; } private void addUsage(Job j){ j.addJobChangeListener(new JobChangeAdapter(){ /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent) */ @Override public void done(IJobChangeEvent event) { if (event.getResult().isOK() && BuildWrapperPlugin.getDefault()!=null){ UsageThread ut=BuildWrapperPlugin.getDefault().getUsageThread(); if (ut!=null){ ut.addProject(getProject()); } } } }); } /** * @return the synchronizerule */ public static ISchedulingRule getSynchronizeRule() { return synchronizeRule; } public void synchronize(final boolean force) { realFacade.getSynchronizeJobQueue().addJob(getSynchronizeJob(force,true)); } public void outline(final IFile f,final IDocument d,final OutlineHandler handler){ final String jobNamePrefix = NLS.bind(BWText.outline_job_name, getProject().getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); OutlineResult or=realFacade.outline(f,d); if(!monitor.isCanceled()){ handler.handleOutline(or); } } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setRule( f ); buildJob.setPriority(Job.SHORT); buildJob.schedule(); } //final IDocument doc, /** * update the info the editor needs * @param file * @param handler * @param ndhandler */ public void updateFromEditor(final IFile file,final IDocument d,final OutlineHandler handler,final NameDefHandler ndhandler,final boolean sync1,final boolean end,final List<? extends EvalHandler> handlers){ final String jobNamePrefix = NLS.bind(BWText.editor_job_name, file.getName(), getProject().getName()); /* * we're not automatically building, and it's the first time we get a request from the editor * we're going to call a full synchronize which in turn will request a configure * so that we're ready to build our haskell files */ final boolean needSynchronize=!realFacade.hasEditorSynchronizeQueue(file) && !ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding(); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(final IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); Runnable r=new Runnable() { @Override public void run() { long t0=System.currentTimeMillis(); if (needSynchronize){ realFacade.synchronize(false); } else if (sync1){ realFacade.synchronize1(file, true); } long t1=System.currentTimeMillis(); if (handler!=null){ OutlineResult or=realFacade.outline(file,d); handler.handleOutline(or); // avoid removing all outline on error } long t3=System.currentTimeMillis(); Collection<NameDef> ns=BWFacade.longRunning? realFacade.build1LongRunning(file,d,end) :realFacade.build1(file, d); long t4=System.currentTimeMillis(); if (ndhandler!=null){ ndhandler.handleNameDefs(ns); } long t5=System.currentTimeMillis(); if (BWFacade.logBuildTimes){ BuildWrapperPlugin.logInfo("sync:"+(t1-t0)+"ms,outline:"+(t3-t1)+"ms,build:"+(t4-t3)+"ms,handleNameDefs:"+(t5-t4)+"ms"); } } }; realFacade.waitForThread(r, monitor); if (!end && handlers!=null){ r=new Runnable() { @Override public void run() { long t5=System.currentTimeMillis(); for (EvalHandler h:handlers){ List<EvalResult> lers=realFacade.eval(file, h.getExpression()); if (lers!=null && lers.size()>0){ h.handleResult(lers.get(0)); } if (monitor.isCanceled()){ break; } } if (BWFacade.logBuildTimes){ long t6=System.currentTimeMillis(); BuildWrapperPlugin.logInfo("eval:"+(t6-t5)+"ms"); } } }; realFacade.waitForThread(r, monitor,BuildWrapperPlugin.getMaxEvalTime()); } } finally { monitor.done(); } return Status.OK_STATUS; } }; String path=file.getProjectRelativePath().toOSString(); // schedule using target file to not stop operations on source file (like save...) IResource r=file.getProject().findMember(BWFacade.DIST_FOLDER+"/"+path); buildJob.setRule( r ); buildJob.setPriority(Job.SHORT); SingleJobQueue sjq=realFacade.getEditorSynchronizeQueue(file); sjq.addJob(buildJob); //buildJob.schedule(); } public void getOccurrences(final IFile file,final String token,final OccurrencesHandler handler){ final String jobNamePrefix = NLS.bind(BWText.occurrences_job_name, getProject().getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); handler.handleOccurrences(realFacade.getOccurrences(file, token)); } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setRule( file ); buildJob.setPriority(Job.SHORT); buildJob.schedule(); } public void getThingAtPoint(final IFile file,final Location location,final ThingAtPointHandler handler){ final String jobNamePrefix = NLS.bind(BWText.thingatpoint_job_name, getProject().getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); //long t0=System.currentTimeMillis(); handler.handleThing(realFacade.getThingAtPoint(file, location)); //long t1=System.currentTimeMillis(); //BuildWrapperPlugin.logInfo("thingAtPoint:"+(t1-t0)+"ms"); } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setRule( file ); buildJob.setPriority(Job.SHORT); //buildJob.schedule(); realFacade.getThingAtPointJobQueue(file).addJob(buildJob); } public IProject getProject() { return realFacade.getProject(); } public void cleanImport(final IFile file,final boolean format, final ImportCleanHandler handler){ final String jobNamePrefix = NLS.bind(BWText.job_import_clean, file.getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); //long t0=System.currentTimeMillis(); handler.handleImportCleans(realFacade.cleanImports(file,format)); //long t1=System.currentTimeMillis(); //BuildWrapperPlugin.logInfo("thingAtPoint:"+(t1-t0)+"ms"); } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setRule( file ); buildJob.setPriority(Job.SHORT); //buildJob.schedule(); realFacade.getThingAtPointJobQueue(file).addJob(buildJob); } /** * launch evaluation of all handlers * @param file * @param handlers */ public void eval(final IFile file,final List<? extends EvalHandler> handlers){ final String jobNamePrefix = NLS.bind(BWText.job_eval, file.getName()); Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); Runnable r=new Runnable(){ @Override public void run() { for (EvalHandler h:handlers){ List<EvalResult> lers=realFacade.eval(file, h.getExpression()); if (lers!=null && lers.size()>0){ h.handleResult(lers.get(0)); } } } }; realFacade.waitForThread(r, monitor,BuildWrapperPlugin.getMaxEvalTime()); } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setRule( file ); buildJob.setPriority(Job.SHORT); //buildJob.schedule(); realFacade.getThingAtPointJobQueue(file).addJob(buildJob); } public static void installDeps(final Set<IProject> projects){ final String jobNamePrefix = BWText.job_deps; Job buildJob = new Job (jobNamePrefix) { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask(jobNamePrefix, IProgressMonitor.UNKNOWN); Set<BWFacade> fcds=new HashSet<>(); for (IProject p:projects){ BWFacade f=BuildWrapperPlugin.getFacade( p ); if (f!=null){ try { SandboxHelper.installDeps( f, fcds ); } catch (Exception ioe){ BuildWrapperPlugin.logError(ioe.getLocalizedMessage(),ioe); } } } } finally { monitor.done(); } return Status.OK_STATUS; } }; buildJob.setPriority(Job.BUILD); buildJob.schedule(); } }