package com.antfortune.freeline.idea.utils;
import com.antfortune.freeline.idea.actions.UpdateAction;
import com.android.tools.idea.gradle.dsl.model.GradleBuildModel;
import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel;
import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencySpec;
import com.android.tools.idea.gradle.parser.GradleBuildFile;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessTerminatedListener;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.psi.PsiFile;
import com.intellij.ui.content.impl.ContentImpl;
import com.antfortune.freeline.idea.icons.PluginIcons;
import com.antfortune.freeline.idea.models.*;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import java.util.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Freeline Utility
*
* @author act262@gmail.com
*/
public class FreelineUtil {
// TODO: 2016/9/13 0013 need refactor tool window
private final static String TOOL_ID = "Freeline Console";
public static void build(Project project) {
GeneralCommandLine commandLine = new GeneralCommandLine();
commandLine.setWorkDirectory(project.getBasePath());
commandLine.setExePath("python");
commandLine.addParameter("freeline.py");
// debug
commandLine.addParameter("-d");
// commands process
try {
processCommandline(project, commandLine);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/* process command line */
private static void processCommandline(final Project project, GeneralCommandLine commandLine) throws ExecutionException {
final OSProcessHandler processHandler = new OSProcessHandler(commandLine);
ProcessTerminatedListener.attach(processHandler);
processHandler.startNotify();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
processConsole(project, processHandler);
}
});
}
/* process attach to console,show the log */
// TODO: 2016/9/14 0014 need refactor console method
private static void processConsole(Project project, ProcessHandler processHandler) {
ConsoleView consoleView = FreeUIManager.getInstance(project).getConsoleView(project);
consoleView.clear();
consoleView.attachToProcess(processHandler);
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
ToolWindow toolWindow;
toolWindow = toolWindowManager.getToolWindow(TOOL_ID);
// if already exist tool window then show it
if (toolWindow != null) {
toolWindow.show(null);
return;
}
toolWindow = toolWindowManager.registerToolWindow(TOOL_ID, true, ToolWindowAnchor.BOTTOM);
toolWindow.setTitle("free....");
toolWindow.setStripeTitle("Free Console");
toolWindow.setShowStripeButton(true);
toolWindow.setIcon(PluginIcons.ICON_TOOL_WINDOW);
toolWindow.getContentManager().addContent(new ContentImpl(consoleView.getComponent(), "Build", true));
toolWindow.show(null);
}
/**
* if had init freeline return true
*/
public static boolean hadInitFreeline(Project project) {
if (project != null) {
String projectPath = project.getBasePath();
// freeline directory
File freelineDir = new File(projectPath, "freeline");
// freeline.py file
File freeline_py = new File(projectPath, "freeline.py");
if (freelineDir.exists() && freeline_py.exists()) {
return true;
}
}
return false;
}
/**
* 是否载入freeline
*
* @param project
* @return
*/
public static boolean hasInitFreeline(@NotNull Project project) {
return getFreelineStatus(project).hasInitFreeline();
}
/**
* 获取freeline安装状态
*
* @param project
* @return
*/
public static FreelineStatus getFreelineStatus(@NotNull Project project) {
FreelineStatus status = new FreelineStatus();
Collection<VirtualFile> gradleFiles = GradleUtil.getAllGradleFile(project);
status.setGradleBuildFiles(gradleFiles);
for (VirtualFile file : gradleFiles) {
if (!status.isExistClasspath()) {
GradleBuildModel model = GradleBuildModel.parseBuildFile(file, project);
if (model != null) {
List<ArtifactDependencyModel> classPaths = model.buildscript().dependencies().artifacts();
for (ArtifactDependencyModel classpath : classPaths) {
ArtifactDependencyModelWrapper wrapper = new ArtifactDependencyModelWrapper(classpath);
if (wrapper.group().equals(Constant.FREELINE_CLASSPATH_GROUP)
&& wrapper.name().equals(Constant.FREELINE_CLASSPATH_ARTIFACT)) {
status.setClasspathFile(file);
break;
}
}
}
}
// 正则二次判断是否存在Freeline classpath
if (!status.isExistClasspath() && regularExistFreelineClassPath(file)) {
status.setClasspathFile(file);
}
if (!status.isExistPlugin()) {
GradleBuildFile gradleBuildFile = new GradleBuildFile(file, project);
if (gradleBuildFile != null) {
List<String> plugins = gradleBuildFile.getPlugins();
if (plugins.contains(Constant.FREELINE_PLUGIN_ID)) {
status.setPluginFile(file);
}
}
}
if (status.isExistClasspath() && status.isExistPlugin()) {
break;
}
}
File baseFile = new File(project.getBasePath());
if (new File(baseFile, Constant.FREELINE_ROOT_FOLDER).exists()
&& new File(baseFile, Constant.FREELINE_PYTHON).exists()) {
if (SystemInfo.isWindows) {
if (new File(baseFile, Constant.FREELINE_ROOT_FOLDER_CORE).exists()) {
status.setExistFreelineCore(true);
}
} else {
status.setExistFreelineCore(true);
}
}
return status;
}
/**
* 检查是否需要载入Freeline
*
* @param project
* @return
*/
public static boolean checkInstall(@NotNull final Project project) {
final FreelineStatus status = getFreelineStatus(project);
if (GradleUtil.isSyncInProgress(project)) {
NotificationUtils.errorMsgDialog("Waiting for sync project to complete");
return false;
}
if (status.hasInitFreeline()) {
return true;
}
if (status.getGradleBuildFiles().size() < 1) {
NotificationUtils.errorMsgDialog("It's not an Android Gradle project Currently?");
return false;
}
if (status.isExistClasspath() && status.isExistPlugin() && !status.isExistFreelineCore()) {
NotificationUtils.errorNotification("Execute task initFreeline and download freeline dependencies...");
initFreeline(project);
return false;
}
if (DialogUtil.createDialog("Detected that you did not installFreeline Freeline, Whether installFreeline Automatically?",
"Install Freeline Automatically", "Cancel")) {
Module[] modules = ModuleManager.getInstance(project).getModules();
List<Pair<Module, PsiFile>> selectModulesList = new ArrayList<Pair<Module, PsiFile>>();
for (Module module : modules) {
GradleBuildFile file = GradleBuildFile.get(module);
if (file != null && !GradleUtil.isLibrary(file)) {
selectModulesList.add(Pair.create(module, file.getPsiFile()));
}
}
// 多个app模块的情况
if (selectModulesList.size() > 1) {
final DialogBuilder builder = new DialogBuilder();
builder.setTitle("Install Freeline");
builder.resizable(false);
builder.setCenterPanel(new JLabel("There are multiple application modules, Please select the module to be installed Freeline.",
Messages.getInformationIcon(), SwingConstants.CENTER));
builder.addOkAction().setText("Cancel");
for (final Pair<Module, PsiFile> pair : selectModulesList) {
builder.addAction(new AbstractAction(":" + pair.first.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
builder.getDialogWrapper().close(DialogWrapper.CANCEL_EXIT_CODE);
installFreeline(project, status, pair.getSecond());
}
});
}
if (builder.show() > -1) {
return false;
}
} else if (selectModulesList.size() == 1) {
installFreeline(project, status, selectModulesList.get(0).getSecond());
} else {
NotificationUtils.errorMsgDialog("Can not found Application Module! Please Sync Project.");
return false;
}
}
return false;
}
/**
* 载入Freeline
*
* @param project
* @param status
* @param psiFile
*/
private static void installFreeline(final Project project, final FreelineStatus status, final PsiFile psiFile) {
ApplicationManager.getApplication().executeOnPooledThread(new UpdateAction.GetServerVersion(new GetServerCallback() {
@Override
public void onSuccess(final GradleDependencyEntity entity) {
LogUtil.d("获取版本号成功:" + entity);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
installFreeline(project, status, psiFile, entity);
}
});
}
@Override
public void onFailure(String errMsg) {
LogUtil.d("获取版本号失败:" + errMsg);
NotificationUtils.errorNotification("Get Freeline Version Failure: " + errMsg);
}
}));
}
private static boolean needReformatCode = false;
private static void installFreeline(final Project project, final FreelineStatus status, final PsiFile psiFile,
final GradleDependencyEntity dependencyEntity) {
needReformatCode = false;
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
if (!status.isExistClasspath()) {
Collection<VirtualFile> collection = status.getGradleBuildFiles();
if (dependencyEntity != null) {
for (VirtualFile file : collection) {
GradleBuildModel model = GradleBuildModel.parseBuildFile(file, project);
List<ArtifactDependencyModel> artifactDependencyModels = model.buildscript().dependencies().artifacts();
for (ArtifactDependencyModel model1 : artifactDependencyModels) {
ArtifactDependencyModelWrapper wrapper = new ArtifactDependencyModelWrapper(model1);
if (wrapper.group().equals(Constant.ANDROID_GRADLE_TOOL_GROUP_NAME)) {
ArtifactDependencySpec spec = new ArtifactDependencySpec(dependencyEntity.getArtifactId(),
dependencyEntity.getGroupId(), dependencyEntity.getNewestReleaseVersion());
model.buildscript().dependencies().addArtifact("classpath", spec);
model.applyChanges();
needReformatCode = true;
status.setClasspathFile(file);
break;
}
}
if (status.isExistClasspath()) {
break;
}
}
}
}
if (!status.isExistPlugin()) {
if (psiFile != null && psiFile instanceof GroovyFile) {
GradleUtil.applyPlugin(project, (GroovyFile) psiFile, Constant.FREELINE_PLUGIN_ID);
}
}
}
});
}
});
if (needReformatCode && status.getClasspathFile() != null) {
DocumentUtil.reformatCode(project, status.getClasspathFile());
}
LogUtil.d("Sync Project Finish, start download freeline.zip.");
initFreeline(project);
}
public static final Pattern PATTERN_CLASSPATH = Pattern.compile("classpath\\s+'"
+ Constant.FREELINE_CLASSPATH_GROUP + ":" + Constant.FREELINE_CLASSPATH_ARTIFACT + ":[\\d|\\.]*'");
/**
* 正则二次判断是否存在Freeline classpath
*
* @param file
* @return
*/
public static boolean regularExistFreelineClassPath(VirtualFile file) {
try {
if (file.exists()) {
String content = FileUtils.readFileToString(new File(file.getPath()));
Matcher matcher = PATTERN_CLASSPATH.matcher(content);
return matcher.find();
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 执行./gradlew initFreeline
* @param project
*/
public static void initFreeline(Project project) {
GradleUtil.executeTask(project, "initFreeline", "-Pmirror", new ExternalSystemTaskNotificationListenerAdapter() {
@Override
public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
super.onTaskOutput(id, text, stdOut);
}
});
}
}