/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.module.impl.ModuleManagerImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.InheritedJdkOrderEntry;
import com.intellij.openapi.roots.ModuleJdkOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCodeFragment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.containers.HashSet;
import com.intellij.util.indexing.IndexingDataKeys;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.module.IExecutionEnvironment;
import gw.lang.reflect.module.IJreModule;
import gw.lang.reflect.module.IModule;
import gw.lang.IModuleAware;
import gw.plugin.ij.core.IJModuleNode;
import gw.plugin.ij.core.PluginLoaderUtil;
import gw.plugin.ij.core.UnidirectionalCyclicGraph;
import gw.plugin.ij.filetypes.GosuProgramFileProvider;
import gw.plugin.ij.lang.psi.api.statements.typedef.IGosuClassDefinition;
import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl;
import gw.plugin.ij.lang.psi.impl.GosuScratchpadFileImpl;
import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuSyntheticClassDefinitionImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
/**
* wrapper for methods in Idea's ModuleUtil class that adds some additional functionality to properly handle
* file fragments. May be obsolete once VirtualFileWindow/DocumentWindow implementation is complete.
*/
public class GosuModuleUtil {
public static final String JUNIT_FRAMEWORK_TEST_CASE = "junit.framework.TestCase";
public static final Key<IModule> KEY_GOSU_MODULE = new Key<>("GosuModule");
@Nullable
public static IModule findModuleForPsiElement(@NotNull PsiElement element) {
if (element instanceof IModuleAware) {
return ((IModuleAware) element).getModule();
}
if( element.getContainingFile() instanceof PsiCodeFragment ) {
element = element.getContainingFile().getContext();
}
if( !isConnected( element ) ) {
return null;
}
IModule module = findGosuModuleForPsiElementInternal(element);
if (module == null) {
final VirtualFile file = element.getUserData(IndexingDataKeys.VIRTUAL_FILE);
if (file != null) {
module = findModuleForFile(file, element.getProject());
}
final PsiFile psiFile = element.getContainingFile();
if (module == null) {
module = getGosuModuleFromOriginalFile(psiFile, element.getProject());
}
if (module == null && psiFile instanceof AbstractGosuClassFileImpl) {
if (!element.equals(psiFile)) {
module = ((AbstractGosuClassFileImpl) psiFile).getModule();
}
}
}
return module;
}
private static boolean isConnected( PsiElement element ) {
if( element instanceof GosuSyntheticClassDefinitionImpl && element.getParent() == null ) {
return false;
}
if( element == null ) {
return true;
}
return isConnected( element.getParent() );
}
@Nullable
public static IModule findModuleForFile(@NotNull VirtualFile file, @NotNull Project project) {
final IModule fileModule = LightVirtualFileWithModule.getModule(file);
if (fileModule != null) {
return fileModule;
}
if (GosuProgramFileProvider.isScratchpad(file)) {
return getGlobalModule(project);
}
IModule gsModule = findGosuModuleByOrderEntries(file, project);
if (gsModule == null) {
gsModule = getModule(ModuleUtil.findModuleForFile(file, project));
}
return gsModule;
}
@Nullable
private static IModule findGosuModuleForPsiElementInternal(@NotNull PsiElement element) {
IModule module = element.getUserData(KEY_GOSU_MODULE);
if (module != null) {
return module;
}
final Project project = element.getProject();
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
if (element instanceof PsiFileSystemItem) {
VirtualFile file = ((PsiFileSystemItem) element).getVirtualFile();
if (file == null) {
final PsiFile psiFile = element.getContainingFile();
file = psiFile == null ? null : psiFile.getOriginalFile().getVirtualFile();
if (file == null) {
return getModule(element.getUserData(ModuleUtil.KEY_MODULE));
}
}
module = findGosuModuleByOrderEntries(file, project);
if (module != null) {
return module;
}
return getModule(fileIndex.getModuleForFile(file));
}
PsiFile psiFile = element.getContainingFile();
if (psiFile != null) {
PsiElement context;
while ((context = psiFile.getContext()) != null) {
final PsiFile file = context.getContainingFile();
if (file == null) {
break;
}
psiFile = file;
}
if (psiFile.getUserData(ModuleUtil.KEY_MODULE) != null) {
return getModule(psiFile.getUserData(ModuleUtil.KEY_MODULE));
}
final PsiFile originalFile = psiFile.getOriginalFile();
if (originalFile.getUserData(ModuleUtil.KEY_MODULE) != null) {
return getModule(originalFile.getUserData(ModuleUtil.KEY_MODULE));
}
final VirtualFile virtualFile = originalFile.getVirtualFile();
IModule fileModule = null;
if(virtualFile != null) {
fileModule = LightVirtualFileWithModule.getModule(virtualFile);
}
if (fileModule != null) {
return fileModule;
} else if (originalFile.getName().startsWith(GosuScratchpadFileImpl.GOSU_SCRATCHPAD_NAME)) {
return getGlobalModule(originalFile.getProject());
} else if (virtualFile != null) {
Module moduleForFile = fileIndex.getModuleForFile(virtualFile);
if (moduleForFile != null) {
return getModule(moduleForFile);
}
module = findGosuModuleByOrderEntries(virtualFile, project);
if (module != null) {
return module;
}
}
}
return getModule(element.getUserData(ModuleUtil.KEY_MODULE));
}
@Nullable
private static IModule getGosuModuleFromOriginalFile(@Nullable PsiFile containingFile, @NotNull Project project) {
// TODO the duplication here is related to locating the module for a Gosu fragment - need to implement VirtualFileWindow
// for fragments to eliminate it.
IModule gosuModule = null;
if (containingFile != null) {
PsiFile originalFile = containingFile.getOriginalFile();
SmartPsiElementPointer owningFile = originalFile.getUserData(FileContextUtil.INJECTED_IN_ELEMENT);
if (owningFile != null) {
VirtualFile owningVF = owningFile.getVirtualFile();
if (owningVF != null) {
gosuModule = findModuleForFile(owningVF, project);
}
} else if (originalFile instanceof GosuScratchpadFileImpl) {
// fragment editor created, but not initialized yet. Pass uber module to keep system happy.
gosuModule = getGlobalModule(project);
}
}
if (gosuModule == null && containingFile != null) {
VirtualFile file = containingFile.getVirtualFile();
if (file instanceof VirtualFileWindow) {
file = ((VirtualFileWindow) file).getDelegate();
}
if (file instanceof LightVirtualFile) {
VirtualFile originalFile = ((LightVirtualFile) file).getOriginalFile();
file = originalFile != null ? originalFile : file;
}
if (file != null) {
return findModuleForFile(file, project);
}
}
return gosuModule;
}
@Nullable
private static IModule findGosuModuleByOrderEntries(@NotNull VirtualFile file, @NotNull Project project) {
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
if (project.isInitialized() && (fileIndex.isInLibrarySource(file) || fileIndex.isInLibraryClasses(file))) {
final List<OrderEntry> entries = fileIndex.getOrderEntriesForFile(file);
if (entries.isEmpty()) {
return null;
}
Set<Module> modules = new HashSet<>();
for (OrderEntry entry : entries) {
if (entry instanceof InheritedJdkOrderEntry || entry instanceof ModuleJdkOrderEntry) {
return GosuModuleUtil.getJreModule(project);
}
modules.add(entry.getOwnerModule());
}
final List<Module> sortedModules = Ordering.from(ModuleManager.getInstance(project).moduleDependencyComparator()).sortedCopy(modules);
return getModule(sortedModules.iterator().next());
}
return null;
}
// Gosu Module <-> IJ Module
@Nullable
public static IModule getModule(@Nullable Module ijModule) {
return ijModule != null ? TypeSystem.getExecutionEnvironment( PluginLoaderUtil.getFrom( ijModule.getProject() ) ).getModule(ijModule.getName()) : null;
}
@Nullable
public static Module getModule(@Nullable IModule gsModule) {
return gsModule != null ? (Module) gsModule.getNativeModule() : null;
}
public static boolean isGosuTest( @Nullable String fqn, @Nullable String pkg, @Nullable Set<String> patterns, Module ijMod, Project proj ) {
if (patterns != null && !patterns.isEmpty()) {
return true;
}
IModule module = GosuModuleUtil.getModule(ijMod);
if (module == null) {
return true;
}
TypeSystem.pushModule(module);
try {
if (fqn != null) {
return TypeSystem.getByFullNameIfValid(fqn, module) instanceof IGosuClass;
}
else if (pkg != null) {
if (pkg.length() == 0) { // a source folder is being run
return true;
}
PsiPackage thePackage = JavaPsiFacadeUtil.findPackage(proj, pkg);
PsiClass[] classes = thePackage.getClasses();
for (PsiClass psiClass : classes) {
if (psiClass instanceof IGosuClassDefinition && extendsTestCase(psiClass)) {
return true;
}
}
return false;
}
else {
return true;
}
}
finally {
TypeSystem.popModule(module);
}
}
public static String getActualClassName( String name, Project project ) {
if( name.startsWith( GosuScratchpadFileImpl.FQN ) ) {
VirtualFile file = GosuScratchpadFileImpl.getScratchpadFile( project );
String scratchPadFile = file.getCanonicalPath();
int iIndex = scratchPadFile.indexOf( '/' );
if( iIndex >= 0 ) {
// remove root or drive
scratchPadFile = scratchPadFile.substring( iIndex+1 );
}
iIndex = scratchPadFile.lastIndexOf( '.' );
scratchPadFile = scratchPadFile.substring( 0, iIndex );
name = name.replace( GosuScratchpadFileImpl.FQN, scratchPadFile );
name = name.replace( '/', '.' );
}
return name;
}
private static boolean extendsTestCase(@Nullable PsiClass psiClass) {
while (psiClass != null && !JUNIT_FRAMEWORK_TEST_CASE.equals(psiClass.getQualifiedName())) {
psiClass = psiClass.getSuperClass();
}
return psiClass != null && JUNIT_FRAMEWORK_TEST_CASE.equals(psiClass.getQualifiedName());
}
public static List<? extends IModule> getModules(@NotNull Project project) {
return TypeSystem.getExecutionEnvironment(PluginLoaderUtil.getFrom(project)).getModules();
}
public static IModule getGlobalModule(@NotNull Project project) {
return TypeSystem.getExecutionEnvironment(PluginLoaderUtil.getFrom(project)).getGlobalModule();
}
@NotNull
public static IJreModule getJreModule(@NotNull Project project) {
return (IJreModule)TypeSystem.getExecutionEnvironment(PluginLoaderUtil.getFrom(project)).getJreModule();
}
public static IModule findOrGlobal(String module) {
IExecutionEnvironment env = TypeSystem.getExecutionEnvironment();
IModule moduleInstance = env.getModule(module);
if (moduleInstance != null) {
return moduleInstance;
}
return env.getGlobalModule();
}
@Nullable
public static String getCircularModuleDependency(@NotNull Project project) {
final UnidirectionalCyclicGraph<Module> graph = new UnidirectionalCyclicGraph<>();
final ModuleManagerImpl instance = (ModuleManagerImpl) ModuleManagerImpl.getInstance(project);
for (Module module : instance.getModules()) {
final IJModuleNode node = new IJModuleNode(module);
graph.registerNode(node.getId(), node);
}
try {
graph.resolveLinks();
return null;
} catch (Exception e) {
return e.getMessage();
}
}
public static List<Module> getDependencies(@NotNull Module module) {
return ImmutableList.copyOf(ModuleRootManager.getInstance(module).getDependencies());
}
}