package intellijcoder.idea; import com.intellij.execution.*; import com.intellij.execution.actions.RunConfigurationProducer; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.execution.junit.JUnitConfiguration; import com.intellij.execution.junit.JUnitUtil; import com.intellij.execution.junit.TestClassConfigurationProducer; import com.intellij.ide.DataManager; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.psi.*; import com.intellij.util.PathUtil; import intellijcoder.main.IntelliJCoderException; import intellijcoder.model.SolutionCfg; import java.io.File; /** * Do all the hard work in IDEA UI, creates modules, files, run configurations, etc. * * @author Konstantin Fadeyev * 15.01.11 */ public class IntelliJIDEA implements Ide { private static final String MESSAGE_BOXES_TITLE = "IntelliJCoder"; private Project project; public IntelliJIDEA(Project project) { this.project = project; } public void createModule(final String moduleName, final String className, final String classSource, final String testSource, final String htmlSource, final int memLimit) { //We run it in the event thread, so the DataContext would have current Project data; DumbService.getInstance(project).smartInvokeLater(new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { try { IntelliJIDEA.this.createModule(getCurrentProject(), moduleName, className, classSource, testSource, htmlSource, memLimit); } catch (IntelliJCoderException e) { showErrorMessage("Failed to create problem workspace. " + e.getMessage()); } } }); } }); } static void showErrorMessage(String errorMessage) { Messages.showErrorDialog(errorMessage, MESSAGE_BOXES_TITLE); } public String getClassSource(final String className) { final String[] result = new String[1]; ApplicationManager.getApplication().invokeAndWait(new Runnable() { public void run() { ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { try { result[0] = IntelliJIDEA.this.getClassSource(getCurrentProject(), className); } catch (IntelliJCoderException e) { showErrorMessage(e.getMessage()); } } }); } }, ModalityState.NON_MODAL); return result[0]; } private Project getCurrentProject() throws IntelliJCoderException { //if project was closed if(!project.isInitialized()) { // we try to locate project by currently focused component @SuppressWarnings({"deprecation"}) DataContext dataContext = DataManager.getInstance().getDataContext(); project = DataKeys.PROJECT.getData(dataContext); } if(project == null) { throw new IntelliJCoderException("There is no opened project."); } return project; } private String getClassSource(Project project, String className) throws IntelliJCoderException { String fileName = classFileName(className); VirtualFile source = null; // find the desired class name across any modules in the project Module[] modules = ModuleManager.getInstance(project).getModules(); for (Module currentModule : modules) { source = findFileInModule(currentModule, fileName); if (source != null) { // found file, stop looking break; } } if (source == null) { throw new IntelliJCoderException("Cannot find file '" + fileName + "'."); } PsiFile psiFile = PsiManager.getInstance(project).findFile(source); assert psiFile != null; return psiFile.getText(); } private VirtualFile findFileInModule(Module module, String fileName) { // module name matches class, so we can look at a specific module VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(); if (sourceRoots.length == 0) { return null; } for (VirtualFile root : sourceRoots) { VirtualFile file = root.findChild(fileName); if (file != null) return file; } return null; } private void createModule(final Project project, String moduleName, String className, String classSource, String testSource, String htmlSource, int memLimit) throws IntelliJCoderException { SolutionCfg config = ConfigurationService.getInstance().getState(); ModuleCreator moduleCreator; switch (config.moduleNamingConvention) { case BY_CLASS_NAME: // this option creates a module per problem moduleCreator = new ModuleCreator(config, project, className, className, classSource, testSource, htmlSource, memLimit); break; case BY_CONTEST_NAME: // this option creates a module per contest moduleCreator = new ModuleCreator(config, project, moduleName, className, classSource, testSource, htmlSource, memLimit); break; default: throw new IntelliJCoderException("Configuration broken. Module naming convention value is not valid."); } moduleCreator.create(); } private static String classFileName(String className) { return className + ".java"; } private static String testClassFileName(String className) { return className + "Test.java"; } private static String htmlFileName(String className) { return className + ".html"; } private static class ModuleCreator { private Project project; private String moduleName; private String className; private String classSource; private String testSource; private String htmlSource; PsiFile classFile; PsiFile testFile; PsiFile htmlFile; private Module module; private SolutionCfg config; private int memLimit; public ModuleCreator(SolutionCfg config, Project project, String moduleName, String className, String classSource, String testSource, String htmlSource, int memLimit) { this.config = config; this.project = project; this.moduleName = moduleName; this.className = className; this.classSource = classSource; this.testSource = testSource; this.htmlSource = htmlSource; this.memLimit = memLimit; } public PsiDirectory getOrCreateModuleFolder(PsiDirectory projectRoot, String moduleName) { PsiDirectory result = projectRoot.findSubdirectory(moduleName); if (result == null) { result = projectRoot.createSubdirectory(moduleName); } return result; } /** * A module will be created if it doesn't already exist. * Source, test, and resource folders will be created and added to the module if they don't already exist. */ public void create() { PsiDirectory projectRoot = PsiManager.getInstance(project).findDirectory(project.getBaseDir()); assert projectRoot != null; final PsiDirectory moduleRoot = getOrCreateModuleFolder(projectRoot, moduleName); module = ModuleManager.getInstance(project).findModuleByName(moduleName); // if module doesn't exist, it must be created if (module == null) { DumbService.getInstance(project).runWhenSmart(new Runnable() { public void run() { module = ModuleManager.getInstance(project).newModule(getModuleFilePath(moduleRoot), StdModuleTypes.JAVA.getId()); ModifiableRootModel modifiableRootModel = ModuleRootManager.getInstance(module).getModifiableModel(); modifiableRootModel.inheritSdk(); addJUnitLibraryDependency(modifiableRootModel); modifiableRootModel.commit(); findOrCreateSourceFolder(moduleRoot, config.sourceFolderName, false); findOrCreateSourceFolder(moduleRoot, config.testFolderName, true); findOrCreateSourceFolder(moduleRoot, config.resourceFolderName, false); } }); } // if relevant files (html,class,test) don't exist, they must be created DumbService.getInstance(project).runWhenSmart(new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { htmlFile = findOrCreateFile(project, moduleRoot.findSubdirectory(config.resourceFolderName), htmlFileName(className), StdFileTypes.HTML, htmlSource); classFile = findOrCreateFile(project, moduleRoot.findSubdirectory(config.sourceFolderName), classFileName(className), JavaFileType.INSTANCE, classSource); testFile = findOrCreateFile(project, moduleRoot.findSubdirectory(config.testFolderName), testClassFileName(className), JavaFileType.INSTANCE, testSource); } }); } }); // prepare a run configuration for current test, and run it once DumbService.getInstance(project).runWhenSmart(new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { ProjectView.getInstance(project).selectPsiElement(classFile, false); FileEditorManager.getInstance(project).openFile(classFile.getVirtualFile(), true); RunConfigurationProducer producer = new TestClassConfigurationProducer(); ConfigurationFactory configurationFactory = producer.getConfigurationType().getConfigurationFactories()[0]; RunnerAndConfigurationSettings settings = RunManager.getInstance(project).createRunConfiguration("", configurationFactory); final JUnitConfiguration configuration = (JUnitConfiguration) settings.getConfiguration(); configuration.setModule(module); PsiClass testClass = JUnitUtil.getTestClass(testFile); configuration.beClassConfiguration(testClass); configuration.restoreOriginalModule(module); String vmParameters = configuration.getVMParameters(); if (!vmParameters.isEmpty()) vmParameters += " "; configuration.setVMParameters(vmParameters + "-Xmx" + memLimit + "m"); final RunManagerEx runManager = (RunManagerEx) RunManager.getInstance(project); runManager.setTemporaryConfiguration(settings); Executor executor = ExecutorRegistry.getInstance().getExecutorById(DefaultRunExecutor.EXECUTOR_ID); ProgramRunnerUtil.executeConfiguration(project, settings, executor); } }); } }); } private PsiDirectory findOrCreateSourceFolder(PsiDirectory moduleRoot, String folderName, boolean test) { PsiDirectory directory = moduleRoot.findSubdirectory(folderName); if (directory != null) return directory; // folder not found, must create it ModifiableRootModel modifiableRootModel = ModuleRootManager.getInstance(module).getModifiableModel(); ContentEntry contentEntry = modifiableRootModel.addContentEntry(moduleRoot.getVirtualFile()); directory = moduleRoot.createSubdirectory(folderName); contentEntry.addSourceFolder(directory.getVirtualFile(), test); modifiableRootModel.commit(); return directory; } private PsiFile findOrCreateFile(Project project, PsiDirectory directory, String fileName, FileType type, String source) { PsiFile file = directory.findFile(fileName); if (file != null) return file; file = PsiFileFactory.getInstance(project).createFileFromText(fileName, type, source); file = (PsiFile)directory.add(file); return file; } private String getModuleFilePath(PsiDirectory moduleRoot) { return moduleRoot.getVirtualFile().getPath() + File.separator + moduleName + ".iml"; } private void addJUnitLibraryDependency(ModifiableRootModel moduleRootModel) { // add junit library String junitJarPath = PathUtil.getJarPathForClass(org.junit.Test.class); addLibraryDependency(moduleRootModel, junitJarPath); // if the hamcrest classes are in a separate jar, add it too (as of JUnit 4.11, they are separate) String hamcrestJarPath = PathUtil.getJarPathForClass(org.hamcrest.SelfDescribing.class); if (!hamcrestJarPath.equals(junitJarPath)) { addLibraryDependency(moduleRootModel, hamcrestJarPath); } } private void addLibraryDependency(ModifiableRootModel moduleRootModel, String libPath) { String url = VfsUtil.getUrlForLibraryRoot(new File(libPath)); VirtualFile libVirtFile = VirtualFileManager.getInstance().findFileByUrl(url); final Library jarLibrary = moduleRootModel.getModuleLibraryTable().createLibrary(); final Library.ModifiableModel libraryModel = jarLibrary.getModifiableModel(); assert libVirtFile != null; libraryModel.addRoot(libVirtFile, OrderRootType.CLASSES); libraryModel.commit(); final LibraryOrderEntry orderEntry = moduleRootModel.findLibraryOrderEntry(jarLibrary); if(orderEntry != null) { orderEntry.setScope(DependencyScope.TEST); } } } }