package com.jetbrains.lang.dart.sdk;
import com.google.common.annotations.VisibleForTesting;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformUtils;
import com.intellij.util.SmartList;
import com.jetbrains.lang.dart.DartProjectComponent;
import com.jetbrains.lang.dart.ide.index.DartLibraryIndex;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.*;
public class DartSdkLibUtil {
private static final Logger LOG = Logger.getInstance(DartSdkLibUtil.class.getName());
private static final String[] SDK_LIB_SUBFOLDERS_BLACKLIST = {
"analysis_server",
"analyzer",
"dev_compiler",
"front_end",
"internal",
"kernel",
"profiler",
};
public static boolean isIdeWithMultipleModuleSupport() {
return PlatformUtils.isIntelliJ();
}
public static void ensureDartSdkConfigured(@NotNull final Project project, @NotNull final String sdkHomePath) {
final LibraryTable libraryTable = ProjectLibraryTable.getInstance(project);
final Library library = libraryTable.getLibraryByName(DartSdk.DART_SDK_LIB_NAME);
if (library == null) {
final LibraryTable.ModifiableModel model = libraryTable.getModifiableModel();
createDartSdkLib(project, model, sdkHomePath);
model.commit();
}
else {
final DartSdk sdk = DartSdk.getSdkByLibrary(library);
if (sdk == null || !sdkHomePath.equals(sdk.getHomePath())) {
setupDartSdkRoots(project, library, sdkHomePath);
}
}
}
public static void ensureDartSdkConfigured(@NotNull final Project project,
@NotNull final LibraryTable.ModifiableModel libraryTableModel,
@NotNull final String sdkHomePath) {
final Library library = libraryTableModel.getLibraryByName(DartSdk.DART_SDK_LIB_NAME);
if (library == null) {
createDartSdkLib(project, libraryTableModel, sdkHomePath);
}
else {
final DartSdk sdk = DartSdk.getSdkByLibrary(library);
if (sdk == null || !sdkHomePath.equals(sdk.getHomePath())) {
setupDartSdkRoots(project, library, sdkHomePath);
}
}
}
private static void createDartSdkLib(@NotNull final Project project,
@NotNull final LibraryTable.ModifiableModel libraryTableModel,
@NotNull final String sdkHomePath) {
final Library existingLib = libraryTableModel.getLibraryByName(DartSdk.DART_SDK_LIB_NAME);
if (existingLib != null) {
setupDartSdkRoots(project, existingLib, sdkHomePath);
}
else {
final Library library = libraryTableModel.createLibrary(DartSdk.DART_SDK_LIB_NAME);
setupDartSdkRoots(project, library, sdkHomePath);
}
}
private static void setupDartSdkRoots(@NotNull final Project project, @NotNull final Library library, @NotNull final String sdkHomePath) {
LocalFileSystem.getInstance().refreshAndFindFileByPath(sdkHomePath + "/lib");
final SortedSet<String> roots = getRootUrls(project, sdkHomePath);
if (roots.isEmpty()) return; // corrupted SDK
if (Comparing.haveEqualElements(ArrayUtil.toStringArray(roots), library.getRootProvider().getUrls(OrderRootType.CLASSES))) {
return; // already ok
}
final LibraryEx.ModifiableModelEx libModifiableModel = (LibraryEx.ModifiableModelEx)library.getModifiableModel();
try {
// remove old
for (String url : libModifiableModel.getUrls(OrderRootType.CLASSES)) {
libModifiableModel.removeRoot(url, OrderRootType.CLASSES);
}
for (String url : libModifiableModel.getExcludedRootUrls()) {
libModifiableModel.removeExcludedRoot(url);
}
// add new
for (String root : roots) {
libModifiableModel.addRoot(root, OrderRootType.CLASSES);
}
libModifiableModel.commit();
}
catch (Exception e) {
LOG.warn(e);
Disposer.dispose(libModifiableModel);
}
}
@NotNull
private static SortedSet<String> getRootUrls(@NotNull final Project project, @NotNull final String sdkHomePath) {
final SortedSet<String> result = getRootUrlsFromLibrariesFile(project, sdkHomePath);
if (result.isEmpty() || !result.contains(VfsUtilCore.pathToUrl(sdkHomePath + "/lib/core"))) {
LOG.info("Failed to get useful info from " + sdkHomePath + "/lib/_internal/libraries.dart");
return getRootUrlsFailover(sdkHomePath);
}
return result;
}
@NotNull
@VisibleForTesting
public static SortedSet<String> getRootUrlsFromLibrariesFile(@NotNull Project project, @NotNull String sdkHomePath) {
final Map<String, String> map = DartLibraryIndex.getSdkLibUriToRelativePathMap(project, sdkHomePath);
final SortedSet<String> result = new TreeSet<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (entry.getKey().startsWith("dart:_")) continue; // private libs
final String relPath = entry.getValue();
final int slashIndex = relPath.indexOf("/");
if (slashIndex <= 0) {
LOG.info("Skipping unexpected Dart library path: " + relPath);
continue;
}
result.add(VfsUtilCore.pathToUrl(sdkHomePath + "/lib/" + relPath.substring(0, slashIndex)));
}
return result;
}
@NotNull
@VisibleForTesting
public static SortedSet<String> getRootUrlsFailover(@NotNull final String sdkHomePath) {
final SortedSet<String> result = new TreeSet<>();
final File lib = new File(sdkHomePath + "/lib");
if (!lib.isDirectory()) return result;
final File[] children = lib.listFiles();
if (children == null) return result;
for (File subDir : children) {
final String subDirName = subDir.getName();
if (!subDir.isDirectory()) continue;
if (subDirName.startsWith("_")) continue;
if (ArrayUtil.contains(subDirName, SDK_LIB_SUBFOLDERS_BLACKLIST)) continue;
result.add(VfsUtilCore.pathToUrl(sdkHomePath + "/lib/" + subDirName));
}
return result;
}
public static boolean isDartSdkEnabled(@NotNull final Module module) {
for (final OrderEntry orderEntry : ModuleRootManager.getInstance(module).getOrderEntries()) {
if (isDartSdkOrderEntry(orderEntry)) {
return true;
}
}
return false;
}
public static void enableDartSdk(@NotNull final Module module) {
if (isDartSdkEnabled(module)) return;
final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
try {
modifiableModel.addInvalidLibrary(DartSdk.DART_SDK_LIB_NAME, LibraryTablesRegistrar.PROJECT_LEVEL);
modifiableModel.commit();
}
catch (Exception e) {
LOG.warn(e);
if (!modifiableModel.isDisposed()) modifiableModel.dispose();
}
}
public static void disableDartSdk(@NotNull final Collection<Module> modules) {
if (modules.isEmpty()) return;
final List<ModifiableRootModel> models = new SmartList<>();
for (final Module module : modules) {
final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
for (final OrderEntry orderEntry : modifiableModel.getOrderEntries()) {
if (isDartSdkOrderEntry(orderEntry)) {
modifiableModel.removeOrderEntry(orderEntry);
}
}
models.add(modifiableModel);
}
DartProjectComponent.commitModifiableModels(modules.iterator().next().getProject(), models);
}
public static Collection<Module> getModulesWithDartSdkEnabled(@NotNull final Project project) {
final Collection<Module> result = new SmartList<>();
for (final Module module : ModuleManager.getInstance(project).getModules()) {
if (isDartSdkEnabled(module)) {
result.add(module);
}
}
return result;
}
public static void enableDartSdkForSpecifiedModulesAndDisableForOthers(@NotNull final Project project,
@NotNull final Module[] modulesWithDart) {
final List<ModifiableRootModel> modelsToCommit = new SmartList<>();
for (final Module module : ModuleManager.getInstance(project).getModules()) {
final boolean mustHaveDart = ArrayUtil.contains(module, modulesWithDart);
boolean hasDart = false;
final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
for (final OrderEntry orderEntry : modifiableModel.getOrderEntries()) {
if (isDartSdkOrderEntry(orderEntry)) {
hasDart = true;
if (!mustHaveDart) {
modifiableModel.removeOrderEntry(orderEntry);
}
}
}
if (mustHaveDart && !hasDart) {
modifiableModel.addInvalidLibrary(DartSdk.DART_SDK_LIB_NAME, LibraryTablesRegistrar.PROJECT_LEVEL);
}
if (modifiableModel.isChanged()) {
modelsToCommit.add(modifiableModel);
}
else {
modifiableModel.dispose();
}
}
DartProjectComponent.commitModifiableModels(project, modelsToCommit);
}
public static boolean isDartSdkOrderEntry(@NotNull final OrderEntry orderEntry) {
return orderEntry instanceof LibraryOrderEntry &&
LibraryTablesRegistrar.PROJECT_LEVEL.equals(((LibraryOrderEntry)orderEntry).getLibraryLevel()) &&
DartSdk.DART_SDK_LIB_NAME.equals(((LibraryOrderEntry)orderEntry).getLibraryName());
}
}