package com.google.jstestdriver.idea.execution.settings.ui; import com.google.jstestdriver.idea.execution.generator.JstdConfigGenerator; import com.google.jstestdriver.idea.execution.generator.JstdGeneratedConfigStructure; import com.google.jstestdriver.idea.util.CastUtils; import com.google.jstestdriver.idea.util.TextChangeListener; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileView; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; public class GeneratedConfigTypeComponent extends JPanel { public static final GeneratedConfigTypeComponent INSTANCE = new GeneratedConfigTypeComponent(); private String myJsFilePath; public JComponent createComponent(final Project project, final JsFileRunSettingsSection jsFileRunSettingsSection) { Box box = Box.createHorizontalBox(); box.add(Box.createRigidArea(new Dimension(5, 0))); box.add(new JLabel("JavaScript test file dependencies will be detected automatically.")); box.add(Box.createRigidArea(new Dimension(5, 0))); final JButton saveAsButton = new JButton("Save As..."); saveAsButton.setMnemonic('v'); saveAsButton.addActionListener(new SaveAsActionListener(project)); jsFileRunSettingsSection.addJsFileTextFieldListener(new TextChangeListener() { @Override public void textChanged(String oldText, @NotNull String newText) { myJsFilePath = newText; boolean isFile = new File(myJsFilePath).isFile(); saveAsButton.setEnabled(isFile); } }); box.add(saveAsButton); box.add(Box.createHorizontalGlue()); return box; } private class SaveAsActionListener implements ActionListener { private static final String LAST_OPENED_FILE_PATH = "jstd_last_opened_file_path"; private static final String JSTD_EXT = ".jstd"; private static final String MESSAGE_DIALOG_TITLE = "Saving generated JsTestDriver configuration file"; private final FileFilter JSTD_CONFIG_FILE_FILTER = new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() || f.getName().endsWith(JSTD_EXT); } @Override public String getDescription() { return "JsTestDriver Configuration File (*.jstd)"; } }; private final Project myProject; private SaveAsActionListener(Project project) { myProject = project; } @Override public void actionPerformed(ActionEvent e) { File initialDir = findInitialDir(); JFileChooser fileChooser = createTunedFileChooser(); File jsFile = new File(myJsFilePath); String fileName = FileUtil.getNameWithoutExtension(jsFile).replace('.', '-'); fileChooser.setSelectedFile(new File(initialDir, "generated-" + fileName)); if (fileChooser.showSaveDialog(WindowManager.getInstance().suggestParentWindow(myProject)) != JFileChooser.APPROVE_OPTION) { return; } File selectedFile = fileChooser.getSelectedFile(); if (selectedFile == null) return; PropertiesComponent.getInstance(myProject).setValue(LAST_OPENED_FILE_PATH, selectedFile.getParent()); File outputFile = findOutputFile(selectedFile, fileChooser.getFileFilter()); try { writeJstdConfigTo(outputFile); } catch (Exception ioe) { showFailMessage("Saving failed due to internal fail."); ioe.printStackTrace(); } } private JFileChooser createTunedFileChooser() { File initialDir = findInitialDir(); JFileChooser fileChooser = new JFileChooser(initialDir); fileChooser.setFileView(new FileView() { @Override public Icon getIcon(File f) { if (f.isDirectory()) { return super.getIcon(f); } FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(f.getName()); return fileType.getIcon(); } }); fileChooser.setMultiSelectionEnabled(false); fileChooser.setAcceptAllFileFilterUsed(true); fileChooser.addChoosableFileFilter(JSTD_CONFIG_FILE_FILTER); fileChooser.setFileFilter(JSTD_CONFIG_FILE_FILTER); fileChooser.setDialogTitle("Save As"); return fileChooser; } @Nullable private File findInitialDir() { String path = PropertiesComponent.getInstance(myProject).getValue(LAST_OPENED_FILE_PATH); VirtualFile projectBaseDir = myProject.getBaseDir(); if (path == null && projectBaseDir != null) { path = projectBaseDir.getPath(); } File dir = new File(path); return dir.isDirectory() ? dir : null; } @NotNull private File findOutputFile(@NotNull final File selectedFile, FileFilter selectedFileFilter) { String extension = FileUtil.getExtension(selectedFile.getName()); if (!extension.isEmpty()) { return selectedFile; } if (selectedFile.exists()) { return selectedFile; } if (selectedFileFilter == JSTD_CONFIG_FILE_FILTER) { return new File(selectedFile.getAbsolutePath() + JSTD_EXT); } return selectedFile; } private void writeJstdConfigTo(@NotNull final File outputFile) throws IOException { ResultWithError<JstdGeneratedConfigStructure, String> configStructureResult = buildConfigStructureResult(); String errorMessage = configStructureResult.getError(); if (errorMessage != null) { showFailMessage(errorMessage); return; } File outputDir = outputFile.getParentFile(); if (!outputDir.exists()) { showFailMessage("Directory " + outputDir.getAbsolutePath() + " does not exists."); return; } if (!outputDir.isDirectory()) { showFailMessage(outputDir.getAbsolutePath() + " should be a directory."); return; } if (outputFile.exists()) { if (outputFile.isFile()) { int retCode = Messages.showYesNoDialog(myProject, outputFile.getAbsolutePath() + " already exists.\nDo you want to replace it?", "Confirm Save As", Messages.getWarningIcon()); if (retCode == 1) { return; } } else { showFailMessage(outputFile + " exists, but it is not a file."); return; } } final JstdGeneratedConfigStructure configStructure = configStructureResult.getResult(); final String content = configStructure.asFileContent(); final VirtualFile outputDirVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(outputDir); if (outputDirVirtualFile == null) { throw new RuntimeException("Can't find VirtualFile for outputDir!"); } try { final AtomicReference<IOException> refToIoException = new AtomicReference<IOException>(); final VirtualFile outputFileVirtualFile = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() { @Override public VirtualFile compute() { try { return outputDirVirtualFile.findOrCreateChildData(null, outputFile.getName()); } catch (IOException e) { refToIoException.set(e); return null; } } }); if (outputFileVirtualFile == null) { IOException ioe = refToIoException.get(); if (ioe != null) { throw ioe; } showUnableToWriteTo(outputFile); return; } final Document document = FileDocumentManager.getInstance().getDocument(outputFileVirtualFile); if (document == null) { showUnableToWriteTo(outputFile); return; } CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { @Override public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { document.replaceString(0, document.getTextLength(), content); PsiDocumentManager.getInstance(myProject).commitDocument(document); } }); } }, "SaveAsGeneratedJstdConfig", null, document); showSuccessMessage(outputFile); } catch (IOException e) { VirtualFile outputVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(outputFile); if (outputVirtualFile == null || !outputVirtualFile.exists()) { showFailMessage("Unable to create " + outputFile.getAbsolutePath()); } else { showUnableToWriteTo(outputFile); } } } @NotNull private ResultWithError<JstdGeneratedConfigStructure, String> buildConfigStructureResult() { return ApplicationManager.getApplication().runReadAction(new Computable<ResultWithError<JstdGeneratedConfigStructure, String>>() { @Override public ResultWithError<JstdGeneratedConfigStructure, String> compute() { File jsIoFile = new File(myJsFilePath); VirtualFile jsVirtualFile = LocalFileSystem.getInstance().findFileByIoFile(jsIoFile); if (jsVirtualFile == null || !jsVirtualFile.isValid()) { return ResultWithError.newError("Could not find JavaScript file at " + jsIoFile.getAbsolutePath()); } PsiFile psiFile = PsiManager.getInstance(myProject).findFile(jsVirtualFile); JSFile jsPsiFile = CastUtils.tryCast(psiFile, JSFile.class); if (jsPsiFile == null) { return ResultWithError.newError("Could not process " + jsIoFile.getAbsolutePath() + " as JavaScript file."); } JstdConfigGenerator generator = JstdConfigGenerator.INSTANCE; JstdGeneratedConfigStructure configStructure = generator.generateJstdConfigStructure(jsPsiFile); return ResultWithError.newResult(configStructure); } }); } private void showUnableToWriteTo(File outputFile) { showFailMessage("Unable to write to " + outputFile.getAbsolutePath()); } private void showFailMessage(String errorMessage) { Messages.showErrorDialog(errorMessage, MESSAGE_DIALOG_TITLE); } private void showSuccessMessage(File outputFile) { Messages.showInfoMessage("JsTestDriver configuration file was saved to " + outputFile.getAbsolutePath(), MESSAGE_DIALOG_TITLE); } } private static class ResultWithError<R, E> { private final R myResult; private final E myError; private ResultWithError(R result, E error) { myResult = result; myError = error; } public R getResult() { return myResult; } public E getError() { return myError; } public static <R, E> ResultWithError<R, E> newError(E error) { return new ResultWithError<R, E>(null, error); } public static <R, E> ResultWithError<R, E> newResult(R result) { return new ResultWithError<R, E>(result, null); } } }