/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.core;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.DumbServiceImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.vfs.VirtualFile;
import gw.fs.IDirectory;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.module.Dependency;
import gw.lang.reflect.module.IExecutionEnvironment;
import gw.lang.reflect.module.IGlobalModule;
import gw.lang.reflect.module.IJreModule;
import gw.lang.reflect.module.IModule;
import gw.plugin.ij.util.ExceptionUtil;
import gw.plugin.ij.util.FileUtil;
import gw.plugin.ij.util.GosuBundle;
import gw.plugin.ij.util.GosuMessages;
import gw.plugin.ij.util.GosuModuleUtil;
import gw.util.fingerprint.FP64;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class ModuleClasspathListener implements ModuleRootListener {
private static final Logger LOG = Logger.getInstance(ModuleClasspathListener.class);
public static boolean ENABLED = true;
public static final String ARROW = " <- ";
public static final String EXPORT = "^, ";
public static final String NOT_EXPORT = ", ";
@Override
public void beforeRootsChange(ModuleRootEvent event) {
}
@Override
public void rootsChanged(@NotNull ModuleRootEvent event) {
Project project = (Project) event.getSource();
if (!shouldProcessRootChanges(project)) {
return;
}
boolean processDependencies = true;
PluginLoaderUtil loader = PluginLoaderUtil.instance(project);
String circularDependency = GosuModuleUtil.getCircularModuleDependency(project);
if (circularDependency != null) {
ExceptionUtil.showWarning(GosuBundle.message("error.dependency.line1"), GosuBundle.message("error.dependency.line2", circularDependency));
processDependencies = false;
} else if (!loader.isStarted() && loader.getFailureReason() == PluginFailureReason.CIRCULAR_DEPENDENCY) {
startPlugin(project, GosuBundle.message("error.dependency.resolved"));
return;
}
if (loader.isStarted()) {
if (processDependencies) {
processModuleDependenciesChange(project);
}
final Module[] modules = ModuleManager.getInstance(project).getModules();
for (Module ijModule : modules) {
IModule gosuModule = TypeSystem.getExecutionEnvironment( PluginLoaderUtil.getFrom( project ) ).getModule(ijModule.getName());
processClasspathChange(gosuModule, ijModule);
changeSourceRoots(gosuModule, ijModule);
}
}
processSDK(project);
}
private boolean shouldProcessRootChanges(Project project) {
if (!ENABLED) {
return false;
}
PluginLoaderUtil loader = PluginLoaderUtil.instance(project);
if (loader.isStarted()) {
return true;
} else {
return loader.getFailureReason() != PluginFailureReason.NONE;
}
}
private void processSDK(@NotNull final Project project) {
ProjectRootManager rootMgr = ProjectRootManagerEx.getInstance(project);
Sdk ijSDK = rootMgr.getProjectSdk();
if (ijSDK == null) {
stopPlugin(project, GosuBundle.message("error.no.project.sdk"));
} else if (!PluginLoaderUtil.instance(project).isStarted()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ExceptionUtil.showWarning(GosuBundle.message("info.plugin.not.started"), GosuBundle.message("info.restart.ide"));
}
});
} else {
Sdk gosuSDK = (Sdk)GosuModuleUtil.getJreModule(project).getNativeSDK();
if (isSDKChanged(project, gosuSDK, ijSDK) && !DumbServiceImpl.getInstance(project).isDumb()) {
refreshJreModule( project );
}
}
}
private boolean isSDKChanged(Project project, Sdk gosuSDK, @NotNull Sdk ijSDK) {
return gosuSDK != ijSDK || !getJrePaths(project).equals(getSDKPaths(ijSDK));
}
private Set<IDirectory> getJrePaths(Project project) {
final IModule jreModule = GosuModuleUtil.getJreModule(project);
final Set<IDirectory> paths = Sets.newHashSet(jreModule.getJavaClassPath());
for (IDirectory dir : jreModule.getSourcePath()) {
paths.add(dir);
}
return paths;
}
private Set<String> getSDKPaths(@NotNull Sdk sdk) {
final Set<String> paths = Sets.newHashSet();
for (VirtualFile file : sdk.getRootProvider().getFiles(OrderRootType.CLASSES)) {
paths.add(FileUtil.removeJarSeparator(file.getPath()));
}
return paths;
}
private void processClasspathChange(@NotNull IModule gosuModule, Module ijModule) {
List<IDirectory> ijClasspath = TypeSystemStarter.getClassPaths(ijModule);
List<IDirectory> ijSources = TypeSystemStarter.getSourceFolders(ijModule);
List<IDirectory> gosuClasspath = gosuModule.getJavaClassPath();
if (areDifferentIgnoringOrder(ijClasspath, gosuClasspath)) {
changeClasspath(gosuModule, ijClasspath, ijSources);
}
}
private boolean areDifferentIgnoringOrder(@NotNull List<IDirectory> list1, @NotNull List<IDirectory> list2) {
if (list1.size() != list2.size()) {
return true;
}
List list1copy = new ArrayList<>(list1);
list1copy.removeAll(list2);
return list1copy.size() != 0;
}
private void processModuleDependenciesChange(@NotNull Project project) {
FP64 ijDependencyFingerprint = computeIJDependencyFingerprint(project);
FP64 gosuDependencyFingerprint = computeGosuDependencyFingerprint(project);
if (!ijDependencyFingerprint.equals(gosuDependencyFingerprint)) {
changeDependencies(project);
}
}
@NotNull
private FP64 computeGosuDependencyFingerprint( Project project ) {
List<String> strings = new ArrayList<>();
for (IModule module : GosuModuleUtil.getModules(project)) {
if (!(module instanceof IJreModule) && !(module instanceof IGlobalModule)) {
String s = module.getName() + ARROW;
for (Dependency dependency : module.getDependencies()) {
final IModule child = dependency.getModule();
if (!(child instanceof IJreModule)) {
s += child.getName() + (dependency.isExported() ? EXPORT : NOT_EXPORT);
}
}
strings.add(s);
}
}
return computeOrderIndependentFingerprint(strings);
}
@NotNull
private FP64 computeIJDependencyFingerprint(@NotNull Project project) {
List<String> strings = new ArrayList<>();
Module[] ijModules = ModuleManager.getInstance(project).getModules();
for (Module ijModule : ijModules) {
String s = ijModule.getName() + ARROW;
for (Module child : ModuleRootManager.getInstance(ijModule).getDependencies()) {
s += child.getName() + (TypeSystemStarter.isExported(ijModule, child) ? EXPORT : NOT_EXPORT);
}
strings.add(s);
}
return computeOrderIndependentFingerprint(strings);
}
@NotNull
private FP64 computeOrderIndependentFingerprint(@NotNull List<String> strings) {
Collections.sort(strings);
final FP64 fp = new FP64();
for (String s : strings) {
fp.extend(s);
}
return fp;
}
// ================================ private part
private void startPlugin(final Project project, String message) {
ExceptionUtil.showInfo(GosuBundle.message("info.plugin.enabled"), message);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
PluginLoaderUtil.instance(project).startPLugin();
} catch (Throwable t) {
LOG.error(GosuMessages.create(GosuBundle.message("error.cannot.start.plugin"), t));
}
}
});
}
private void stopPlugin(final Project project, final String message) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
ExceptionUtil.showWarning(GosuBundle.message("info.plugin.disabled"), message);
PluginLoaderUtil.instance(project).closeProject(PluginFailureReason.NO_SDK);
} catch (Throwable t) {
LOG.error(GosuMessages.create(GosuBundle.message("error.cannot.stop.plugin"), t));
}
}
});
}
private void refreshJreModule( final Project project ) {
final PluginLoaderUtil util = PluginLoaderUtil.instance(project);
if (util.isStarted()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
IJreModule jreModule = (IJreModule)TypeSystem.getExecutionEnvironment().getJreModule();
TypeSystemStarter.updateJreModuleWithProjectSdk( project, jreModule );
TypeSystem.refresh( jreModule);
} catch (Throwable t) {
LOG.error(GosuMessages.create(GosuBundle.message("error.cannot.restart.plugin"), t));
}
}
});
}
}
private void changeDependencies(@NotNull Project project) {
final Module[] ijModules = ModuleManager.getInstance(project).getModules();
IExecutionEnvironment execEnv = TypeSystem.getExecutionEnvironment( PluginLoaderUtil.getFrom( project ) );
for (Module ijModule : ijModules) {
IModule gosuModule = execEnv.getModule(ijModule.getName());
final List<Dependency> newDeps = Lists.newArrayList();
for (Module child : ModuleRootManager.getInstance(ijModule).getDependencies()) {
IModule gosuChild = execEnv.getModule(child.getName());
newDeps.add(new Dependency(gosuChild, TypeSystemStarter.isExported(ijModule, child)));
}
newDeps.add(new Dependency(execEnv.getJreModule(), true));
gosuModule.setDependencies(newDeps);
}
TypeSystem.refresh(true);
}
private void changeSourceRoots(@NotNull IModule gosuModule, Module ijModule) {
final List<IDirectory> gosuSourcePaths = gosuModule.getSourcePath();
final List<IDirectory> ijourcePaths = TypeSystemStarter.getSourceFolders(ijModule);
if (areDifferentIgnoringOrder(gosuSourcePaths, ijourcePaths)) {
gosuModule.setSourcePath(ijourcePaths);
TypeSystem.refresh(gosuModule);
}
}
private void changeClasspath(@NotNull IModule gosuModule, List<IDirectory> classpath, List<IDirectory> sources) {
gosuModule.configurePaths(classpath, sources);
TypeSystem.refresh(gosuModule);
}
}