/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.core; import com.google.common.collect.ImmutableSet; import com.intellij.ProjectTopics; import com.intellij.codeInsight.daemon.impl.EditorTracker; import com.intellij.compiler.CompilerWorkspaceConfiguration; import com.intellij.ide.startup.impl.StartupManagerImpl; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompilerManager; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.DumbServiceImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.impl.DefaultProject; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NotNullLazyValue; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.impl.PsiDocumentTransactionListener; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; import gw.config.CommonServices; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.module.IModule; import gw.lang.reflect.module.IProject; import gw.plugin.ij.actions.java.CreateClassAction; import gw.plugin.ij.compiler.DependencyCacheCleaner; import gw.plugin.ij.compiler.GosuCompiler; import gw.plugin.ij.filetypes.GosuCodeFileType; import gw.plugin.ij.util.ExceptionUtil; import gw.plugin.ij.util.GosuBundle; import gw.plugin.ij.util.GosuModuleUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.WeakHashMap; public class PluginLoaderUtil { private static final Map<Project, PluginLoaderUtil> INSTANCES = new WeakHashMap<>(); public static final Key<Disposable> TEST_ROOT_DISPOSABLE = Key.create("testRootDisposable"); public static final Key<IProject> PROJECT_KEY = Key.create("projectKey"); private final Project _project; @Nullable private GosuCompiler _gosuCompiler; @Nullable private FileModificationManager _fileModificationManager; private boolean _startupOk = true; @Nullable private Throwable _startupError = null; @Nullable private MessageBusConnection _projectConnection; private MessageBusConnection _permanentProjectConnection; @Nullable private MessageBusConnection _applicationConnection; private EditorTracker _editorTracker; private static final NotNullLazyValue<ITypeSystemStartupContributor[]> PLUGIN_LISTENERS = new NotNullLazyValue<ITypeSystemStartupContributor[]>() { @NotNull @Override protected ITypeSystemStartupContributor[] compute() { final TypeSystemStartupContributorExtensionBean[] extensions = Extensions.getExtensions(TypeSystemStartupContributorExtensionBean.EP_NAME); final ITypeSystemStartupContributor[] results = new ITypeSystemStartupContributor[extensions.length]; for (int i = 0; i < extensions.length; i++) { results[i] = extensions[i].getHandler(); } return results; } }; private PluginFailureReason _failureReason = PluginFailureReason.NONE; private final ModuleClasspathListener _moduleClasspathListener = new ModuleClasspathListener(); private boolean _guidewireApp; public static PluginLoaderUtil instance(Project project) { PluginLoaderUtil util = INSTANCES.get(project); if (util == null) { INSTANCES.put(project, util = new PluginLoaderUtil(project)); } return util; } private PluginLoaderUtil(Project project) { _project = project; } @NotNull private ITypeSystemStartupContributor[] getStartupContributors() { return PLUGIN_LISTENERS.getValue(); } @NotNull public ModuleClasspathListener getModuleClasspathListener() { return _moduleClasspathListener; } public void addCompiler() { _gosuCompiler = new GosuCompiler(); final CompilerManager manager = CompilerManager.getInstance(_project); manager.addCompilableFileType(GosuCodeFileType.INSTANCE); manager.addTranslatingCompiler(_gosuCompiler, ImmutableSet.<FileType>of(StdFileTypes.JAVA, GosuCodeFileType.INSTANCE), // input ImmutableSet.<FileType>of(StdFileTypes.JAVA, GosuCodeFileType.INSTANCE)); // output // new CompilerLogger(_project).start(); manager.addBeforeTask(new DependencyCacheCleaner()); } public void removeCompiler() { final CompilerManager manager = CompilerManager.getInstance(_project); manager.removeCompilableFileType(GosuCodeFileType.INSTANCE); manager.removeCompiler(_gosuCompiler); _gosuCompiler = null; } private void initGosuPlugin() { final IModule rootModule = GosuModuleUtil.getGlobalModule(_project); TypeSystem.pushModule(rootModule); try { _projectConnection = _project.getMessageBus().connect(); addCompiler(); addTypeRefreshListener(); addModuleRefreshListener(); addEditorSourceProvider(); ActionManagerImpl actionManager = (ActionManagerImpl) ActionManagerImpl.getInstance(); AnAction oldNewClassAction = actionManager.getAction("NewClass"); actionManager.unregisterAction("NewClass"); CreateClassAction action = new CreateClassAction(); actionManager.registerAction("NewClass", action); } finally { TypeSystem.popModule(rootModule); } } private void addEditorSourceProvider() { IDEAPlatformHelper platformHelper = (IDEAPlatformHelper) CommonServices.getPlatformHelper(); _projectConnection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, platformHelper); } private void addTypeRefreshListener() { _fileModificationManager = new FileModificationManager(_project); _projectConnection.subscribe(PsiDocumentTransactionListener.TOPIC, _fileModificationManager); _applicationConnection.subscribe(VirtualFileManager.VFS_CHANGES, _fileModificationManager); } private void addModuleRefreshListener() { ModuleRefreshListener moduleRefreshListener = new ModuleRefreshListener(); _projectConnection.subscribe(ProjectTopics.MODULES, moduleRefreshListener); } public void uninitGosuPlugin() { gw.plugin.ij.util.UIUtil.closeAllGosuEditors(_project, null); //DebuggerManager.getInstance( _project ).unregisterPositionManagerFactory( _positionManager ); removeCompiler(); _projectConnection.disconnect(); _projectConnection = null; _applicationConnection.disconnect(); _applicationConnection = null; _gosuCompiler = null; _fileModificationManager = null; _startupOk = true; _startupError = null; } private void setDumbMode(final boolean dumb) { UIUtil.invokeAndWaitIfNeeded(new Runnable() { public void run() { DumbServiceImpl.getInstance(_project).setDumb(dumb); } }); } public void reportStartupError(@NotNull Throwable e) { disablePlugin(); _startupError = e; if (e instanceof GosuPluginException) { _failureReason = ((GosuPluginException) e).getReason(); } else { _failureReason = PluginFailureReason.EXCEPTION; } ExceptionUtil.showError(GosuBundle.message("error.plugin_could_not_start"), e); } private void disablePlugin() { _startupOk = false; } public boolean isStartupOk() { return _startupOk; } public void setProject() { if (!(_project instanceof DefaultProject)) { _failureReason = PluginFailureReason.NONE; _permanentProjectConnection = _project.getMessageBus().connect(); _permanentProjectConnection.subscribe(ProjectTopics.PROJECT_ROOTS, _moduleClasspathListener); } } @Nullable public Throwable getStartupError() { return _startupError; } public void setEditorTracker(EditorTracker editorTracker) { _editorTracker = editorTracker; } public boolean isStarted() { return TypeSystemStarter.instance(_project).isStarted(); } public void startPLugin() { if (hasRanPreviously() && isGuidewireApp()) { ExceptionUtil.showError(GosuBundle.message("error.plugin_disabled"), GosuBundle.message("error.plugin_disabled.description")); return; } for (ITypeSystemStartupContributor contributor : getStartupContributors()) { String message = contributor.accepts(_project); if (message != null) { ExceptionUtil.showInfo(GosuBundle.message("info.plugin.not.started"), message); return; } } // pre -startup try { for (ITypeSystemStartupContributor pluginListener : getStartupContributors()) { pluginListener.beforeTypesystemStartup(_project); } } catch (Exception e) { reportStartupError(e); return; } // !PW leaks reference to GosuLoader, if we ever want to support clean unloading of plugin _applicationConnection = ApplicationManager.getApplication().getMessageBus().connect(); setDumbMode(true); try { startTypeSystem(); initGosuPlugin(); for (ITypeSystemStartupContributor pluginListener : getStartupContributors()) { pluginListener.afterTypesystemStartup(_project); } } catch (Throwable e) { reportStartupError(e); } finally { setDumbMode(false); } } private boolean hasRanPreviously() { return CommonServices.getPlatformHelper().isInIDE(); } public void closeProject(PluginFailureReason failureReason) { boolean previousValue = ModuleClasspathListener.ENABLED; ModuleClasspathListener.ENABLED = false; try { if (TypeSystemStarter.instance(_project).isStarted()) { try { if (TypeSystem.getCurrentModule() != null) { System.out.println("Cleaning type system, but current top module is not null!"); } stopTypeSystem(); } catch (Throwable e) { reportStartupError(e); } try { uninitGosuPlugin(); } catch (Throwable e) { reportStartupError(e); } try { for (ITypeSystemStartupContributor pluginListener : getStartupContributors()) { pluginListener.afterPluginShutdown(_project); } } catch (Exception e) { ExceptionUtil.showNonFatalError("Exception during Gosu plugin shutdown.", e); } } } finally { _failureReason = failureReason; ModuleClasspathListener.ENABLED = previousValue; } } private void startTypeSystem() { TypeSystemStarter.instance(_project).start(_project); } private void stopTypeSystem() { TypeSystemStarter.instance(_project).stop(_project); } public boolean projectOpened() { // if (ProjectManager.getInstance().getOpenProjects().length > 1) { // ExceptionAUtil.showWarning("Gosu support disabled.", "Gosu cannot support multiple simultaneously open projects."); // stopPLugin(_project, PluginFailureReason.MULTIPLE_PROJECTS_OPEN); // return false; // } final long[] t1 = new long[1]; StartupManagerImpl.getInstance(_project).registerStartupActivity(new Runnable() { public void run() { try { startPLugin(); } finally { t1[0] = System.nanoTime(); } } }); StartupManagerImpl.getInstance(_project).registerPostStartupActivity(new Runnable() { public void run() { System.out.printf("Indexing done in %.3fs.\n", (System.nanoTime() - t1[0]) * 1e-9); } }); return true; } public void projectClosed() { closeProject(PluginFailureReason.NONE); } public PluginFailureReason getFailureReason() { return _failureReason; } @Nullable public static IProject getFrom(@NotNull Project project) { IProject gsProject = project.getUserData(PROJECT_KEY); if (gsProject == null) { project.putUserData(PROJECT_KEY, gsProject = new IjProject(project)); } return gsProject; } public boolean isGuidewireApp() { return !CommonServices.getEntityAccess().getClass().getSimpleName().equals("DefaultEntityAccess"); } public void disableIJExternalCompiler() { CompilerWorkspaceConfiguration workspaceConfiguration = CompilerWorkspaceConfiguration.getInstance(_project); workspaceConfiguration.USE_COMPILE_SERVER = false; } }