package com.vpedak.testsrecorder.plugin.ui;
import com.intellij.ide.util.PackageUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.util.IncorrectOperationException;
import com.vpedak.testsrecorder.core.events.RecordingEvent;
import com.vpedak.testsrecorder.plugin.core.EspressoTestGenerator;
import com.vpedak.testsrecorder.plugin.core.EventReader;
import com.vpedak.testsrecorder.core.TestGenerator;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.java.JavaSourceRootType;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
public class EventsList extends JPanel implements EventReader.EventListener, ComponentListener {
private JBList list;
private DefaultListModel listModel;
private final JBScrollPane pane;
private Project project;
private Module module;
private PsiFile activityFile;
private JTextField testClassField = new JTextField(20);
private String activityClassName;
private JCheckBox timeCheckBox;
private List<RecordingEvent> events = new ArrayList<RecordingEvent>();
public EventsList() {
super(new VerticalLayout(5));
listModel = new DefaultListModel();
final EspressoTestGenerator testGenerator = new EspressoTestGenerator(false);
list = new JBList(listModel) {
@Override
public String getToolTipText(MouseEvent event) {
int idx = locationToIndex(event.getPoint());
if (idx != -1) {
RecordingEvent recordingEvent = (RecordingEvent) getModel().getElementAt(idx);
List<RecordingEvent> tmp = new ArrayList<RecordingEvent>();
tmp.add(recordingEvent);
if (recordingEvent.getGroup() != null) {
int pos = events.indexOf(recordingEvent);
tmp.add(events.get(pos+1));
}
return testGenerator.generateBody(tmp);
} else {
return super.getToolTipText(event);
}
}
};
list.setCellRenderer(new MyRenderer());
list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
list.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger() && list.getSelectedIndex() != -1) {
JPopupMenu menu = new JPopupMenu();
JMenuItem item = new JMenuItem("Generate selected actions to clipboard");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
generateSelected();
}
});
menu.add(item);
menu.show(EventsList.this, e.getX()+20, list.getCellBounds(
list.getSelectedIndex() ,
list.getSelectedIndex() ).y+20);
}
}
});
pane = new JBScrollPane(list);
add(pane);
JPanel tmp = new JPanel();
timeCheckBox = new JCheckBox("Generate delays between actions as it was recorded.");
tmp.add(timeCheckBox);
add(tmp);
final JButton generate = new JButton("Generate Code", IconLoader.getIcon("icons/gen.png"));
generate.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
generate();
}
});
tmp = new JPanel();
JLabel testLabel = new JLabel("Test class: ", SwingConstants.RIGHT);
testLabel.setMaximumSize(new Dimension(testLabel.getPreferredSize().width + 20, testLabel.getPreferredSize().height));
tmp.add(testLabel);
testClassField = new JTextField(20);
testClassField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
generate.setEnabled(testClassField.getText().trim().length() > 0);
}
@Override
public void removeUpdate(DocumentEvent e) {
generate.setEnabled(testClassField.getText().trim().length() > 0);
}
@Override
public void changedUpdate(DocumentEvent e) {
generate.setEnabled(testClassField.getText().trim().length() > 0);
}
});
tmp.add(testClassField);
tmp.add(generate);
add(tmp);
JPanel buttonsPanel = new JPanel();
JButton save = new JButton("Save List", IconLoader.getIcon("icons/save.png"));
save.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
save();
}
});
JButton load = new JButton("Load List", IconLoader.getIcon("icons/load.png"));
load.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
load();
}
});
buttonsPanel.add(save);
buttonsPanel.add(load);
add(buttonsPanel);
addComponentListener(this);
}
private void generateSelected() {
List<RecordingEvent> selected = list.getSelectedValuesList();
int start = events.indexOf(selected.get(0));
RecordingEvent last = selected.get(selected.size()-1);
int end;
if (last.getGroup() == null) {
end = events.indexOf(last);
} else {
String grp = last.getGroup();
for (end = events.indexOf(last); end < events.size(); end++) {
if (!grp.equals(events.get(end).getGroup())) {
break;
}
}
end--;
}
List<RecordingEvent> eventsToGenerate = events.subList(start, end+1);
EspressoTestGenerator testGenerator = new EspressoTestGenerator(timeCheckBox.isSelected());
String code = testGenerator.generateBody(eventsToGenerate);
if (testGenerator.wasAdditionMethods()) {
code = "\t// Custom methods were used in generated code, see details at http://droidtestlab.com/additions.html \n" + code;
}
StringSelection stringSelection = new StringSelection(code);
Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
clpbrd.setContents(stringSelection, stringSelection);
}
public void clear(Project project, Module module, PsiFile activityFile) {
listModel.removeAllElements();
events.clear();
this.project = project;
this.module = module;
this.activityFile = activityFile;
activityClassName = activityFile.getName();
int pos = activityClassName.indexOf('.');
if (pos > 0) {
activityClassName = activityClassName.substring(0, pos);
}
testClassField.setText(activityClassName +"Test");
}
private void generate() {
final String packageName = ((com.intellij.psi.PsiJavaFile) activityFile).getPackageName();
PsiDirectory dir = selectTargetDirectory(packageName);
if (dir == null) {
dir = activityFile.getContainingDirectory();
}
final PsiDirectory finalDir = dir;
final String testClassName = testClassField.getText();
final String testName = testClassName+".java";
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
try {
PsiFile testFile = finalDir.findFile(testName);
if (testFile == null) {
testFile = finalDir.createFile(testName);
}
VirtualFile testVirtualFile = testFile.getVirtualFile();
TestGenerator testGenerator = new EspressoTestGenerator(timeCheckBox.isSelected());
String code = testGenerator.generate(activityClassName, testClassName, packageName, events);
com.intellij.openapi.vfs.VfsUtil.saveText(testVirtualFile, code);
final PsiFile psiFile = testFile;
WriteCommandAction.Simple<String> command = new WriteCommandAction.Simple<String>(project, psiFile) {
@Override
protected void run() throws Throwable {
CodeStyleManager.getInstance(project).reformat(psiFile, false);
}
};
command.execute();
FileEditorManagerEx.getInstanceEx(project).openFile(testVirtualFile, true);
} catch (IOException e) {
Messages.showErrorDialog(project, e.getMessage(), "Error");
}
}
});
}
@Nullable
private PsiDirectory selectTargetDirectory(final String packageName) throws IncorrectOperationException {
final PackageWrapper targetPackage = new PackageWrapper(PsiManager.getInstance(project), packageName);
final VirtualFile selectedRoot = new ReadAction<VirtualFile>() {
protected void run(Result<VirtualFile> result) throws Throwable {
final HashSet<VirtualFile> testFolders = new HashSet<VirtualFile>();
checkForTestRoots(module, testFolders);
List<VirtualFile> roots;
if (testFolders.isEmpty()) {
roots = ModuleRootManager.getInstance(module).getSourceRoots(JavaModuleSourceRootTypes.SOURCES);
removeGenerated(roots);
if (roots.isEmpty()) return;
} else {
roots = new ArrayList<VirtualFile>(testFolders);
}
if (roots.size() == 1) {
result.setResult(roots.get(0));
}
else {
PsiDirectory defaultDir = chooseDefaultDirectory(packageName);
result.setResult(MoveClassesOrPackagesUtil.chooseSourceRoot(targetPackage, roots, defaultDir));
}
}
}.execute().getResultObject();
if (selectedRoot == null) return null;
return new WriteCommandAction<PsiDirectory>(project, "") {
protected void run(Result<PsiDirectory> result) throws Throwable {
result.setResult(RefactoringUtil.createPackageDirectoryInSourceRoot(targetPackage, selectedRoot));
}
}.execute().getResultObject();
}
protected static void checkForTestRoots(Module srcModule, Set<VirtualFile> testFolders) {
testFolders.addAll(ModuleRootManager.getInstance(srcModule).getSourceRoots(JavaSourceRootType.TEST_SOURCE));
removeGenerated(testFolders);
//create test in the same module
if (!testFolders.isEmpty()) return;
//suggest to choose from all dependencies modules
final HashSet<Module> modules = new HashSet<Module>();
ModuleUtilCore.collectModulesDependsOn(srcModule, modules);
for (Module module : modules) {
testFolders.addAll(ModuleRootManager.getInstance(module).getSourceRoots(JavaSourceRootType.TEST_SOURCE));
}
removeGenerated(testFolders);
}
private static void removeGenerated(Collection<VirtualFile> testFolders) {
// remove generated directories
for (Iterator<VirtualFile> iter = testFolders.iterator(); iter.hasNext();) {
VirtualFile tst = iter.next();
String url = tst.getPresentableUrl();
if (url.contains("build/generated") || url.contains("build\\generated")) {
iter.remove();
}
}
}
@Nullable
private PsiDirectory chooseDefaultDirectory(String packageName) {
List<PsiDirectory> dirs = new ArrayList<PsiDirectory>();
for (VirtualFile file : ModuleRootManager.getInstance(module).getSourceRoots(JavaSourceRootType.TEST_SOURCE)) {
final PsiDirectory dir = PsiManager.getInstance(project).findDirectory(file);
if (dir != null) {
dirs.add(dir);
}
}
if (!dirs.isEmpty()) {
for (PsiDirectory dir : dirs) {
final String dirName = dir.getVirtualFile().getPath();
if (dirName.contains("generated")) continue;
return dir;
}
return dirs.get(0);
}
return PackageUtil.findPossiblePackageDirectoryInModule(module, packageName);
}
private void load() {
final JFileChooser fc = new JFileChooser();
fc.setFileFilter(new FileNameExtensionFilter("Recording Files", "atr"));
int res = fc.showOpenDialog(this);
if (res == JFileChooser.APPROVE_OPTION) {
listModel.removeAllElements();
events.clear();
File file = fc.getSelectedFile();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
String str = reader.readLine();
while (str != null) {
RecordingEvent event = RecordingEvent.fromString(str);
events.add(event);
if (event.getDescription() != null) {
listModel.addElement(event);
}
str = reader.readLine();
}
} catch (IOException e) {
Messages.showErrorDialog(project, "Failed to load file " + file.getAbsolutePath(), "Error");
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void save() {
final JFileChooser fc = new JFileChooser();
fc.setFileFilter(new FileNameExtensionFilter("Recording Files", "atr"));
int res = fc.showSaveDialog(this);
if (res == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
if (!file.getAbsolutePath().contains(".")) {
file = new File(file.getAbsolutePath()+".atr");
}
FileWriter writer = null;
try {
writer = new FileWriter(file, false);
for (RecordingEvent event : events) {
writer.write(event.toString()+"\n");
}
} catch (IOException e) {
Messages.showErrorDialog(project, "Failed to save to file " + file.getAbsolutePath(), "Error");
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public void onRecordingEvent(final RecordingEvent event) {
events.add(event);
if (event.getDescription() != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
listModel.addElement(event);
}
});
}
}
@Override
public void componentResized(ComponentEvent e) {
pane.setPreferredSize(new Dimension(getWidth(), getHeight() - 150));
}
@Override
public void componentMoved(ComponentEvent e) {
}
@Override
public void componentShown(ComponentEvent e) {
}
@Override
public void componentHidden(ComponentEvent e) {
}
class MyRenderer extends DefaultListCellRenderer {
public MyRenderer() {
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
}
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
RecordingEvent event = (RecordingEvent) value;
setText(" "+event.getDescription());
return c;
}
}
}