blob: 665039507acedb9a8596a288f9a47af39927b0be [file] [log] [blame]
package org.jetbrains.debugger.sourcemap;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Url;
import com.intellij.util.UrlImpl;
import com.intellij.util.Urls;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ObjectIntHashMap;
import com.intellij.util.io.URLUtil;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.LocalFileFinder;
import java.util.List;
public class SourceResolver {
private final List<String> rawSources;
@Nullable private final List<String> sourcesContent;
final Url[] canonicalizedSources;
private final ObjectIntHashMap<Url> canonicalizedSourcesMap;
private TObjectIntHashMap<String> absoluteLocalPathToSourceIndex;
// absoluteLocalPathToSourceIndex contains canonical paths too, but this map contains only used (specified in the source map) path
private String[] sourceIndexToAbsoluteLocalPath;
public SourceResolver(@NotNull List<String> sourcesUrl, boolean trimFileScheme, @Nullable Url baseFileUrl, @Nullable List<String> sourcesContent) {
rawSources = sourcesUrl;
this.sourcesContent = sourcesContent;
canonicalizedSources = new Url[sourcesUrl.size()];
canonicalizedSourcesMap = SystemInfo.isFileSystemCaseSensitive
? new ObjectIntHashMap<Url>(canonicalizedSources.length)
: new ObjectIntHashMap<Url>(canonicalizedSources.length, Urls.getCaseInsensitiveUrlHashingStrategy());
for (int i = 0; i < sourcesUrl.size(); i++) {
String rawSource = sourcesUrl.get(i);
Url url = canonicalizeUrl(rawSource, baseFileUrl, trimFileScheme, i);
canonicalizedSources[i] = url;
canonicalizedSourcesMap.put(url, i);
}
}
public static boolean isAbsolute(@NotNull String path) {
return !path.isEmpty() && (path.charAt(0) == '/' || (SystemInfo.isWindows && (path.length() > 2 && path.charAt(1) == ':')));
}
// see canonicalizeUri kotlin impl and https://trac.webkit.org/browser/trunk/Source/WebCore/inspector/front-end/ParsedURL.js completeURL
private Url canonicalizeUrl(@NotNull String url, @Nullable Url baseUrl, boolean trimFileScheme, int sourceIndex) {
if (trimFileScheme && url.startsWith(StandardFileSystems.FILE_PROTOCOL_PREFIX)) {
return Urls.newLocalFileUrl(FileUtil.toCanonicalPath(VfsUtilCore.toIdeaUrl(url, true).substring(StandardFileSystems.FILE_PROTOCOL_PREFIX.length()), '/'));
}
else if (baseUrl == null || url.contains(URLUtil.SCHEME_SEPARATOR) || url.startsWith("data:") || url.startsWith("blob:") || url.startsWith("javascript:")) {
return Urls.parseEncoded(url);
}
String path = url;
if (url.charAt(0) != '/') {
String basePath = baseUrl.getPath();
int lastSlashIndex = basePath.lastIndexOf('/');
StringBuilder pathBuilder = new StringBuilder();
if (lastSlashIndex == -1) {
pathBuilder.append(basePath).append('/');
}
else {
pathBuilder.append(basePath, 0, lastSlashIndex + 1);
}
path = pathBuilder.append(url).toString();
}
path = FileUtil.toCanonicalPath(path, '/');
if (baseUrl.getScheme() == null && baseUrl.isInLocalFileSystem()) {
return Urls.newLocalFileUrl(path);
}
// browserify produces absolute path in the local filesystem
if (isAbsolute(path)) {
VirtualFile file = LocalFileFinder.findFile(path);
if (file != null) {
if (absoluteLocalPathToSourceIndex == null) {
// must be linked, on iterate original path must be first
absoluteLocalPathToSourceIndex = createStringIntMap(rawSources.size());
sourceIndexToAbsoluteLocalPath = new String[rawSources.size()];
}
absoluteLocalPathToSourceIndex.put(path, sourceIndex);
sourceIndexToAbsoluteLocalPath[sourceIndex] = path;
String canonicalPath = file.getCanonicalPath();
if (canonicalPath != null && !canonicalPath.equals(path)) {
absoluteLocalPathToSourceIndex.put(canonicalPath, sourceIndex);
}
}
}
return new UrlImpl(baseUrl.getScheme(), baseUrl.getAuthority(), path, null);
}
@NotNull
private static ObjectIntHashMap<String> createStringIntMap(int initialCapacity) {
if (initialCapacity == -1) {
initialCapacity = 4;
}
return SystemInfo.isFileSystemCaseSensitive
? new ObjectIntHashMap<String>(initialCapacity)
: new ObjectIntHashMap<String>(initialCapacity, CaseInsensitiveStringHashingStrategy.INSTANCE);
}
@Nullable
public Url getSource(@NotNull MappingEntry entry) {
int index = entry.getSource();
return index < 0 ? null : canonicalizedSources[index];
}
@Nullable
public String getSourceContent(@NotNull MappingEntry entry) {
if (ContainerUtil.isEmpty(sourcesContent)) {
return null;
}
int index = entry.getSource();
return index < 0 || index >= sourcesContent.size() ? null : sourcesContent.get(index);
}
@Nullable
public String getRawSource(@NotNull MappingEntry entry) {
int index = entry.getSource();
return index < 0 ? null : rawSources.get(index);
}
@Nullable
public String getLocalFilePath(@NotNull MappingEntry entry) {
final int index = entry.getSource();
return index < 0 || sourceIndexToAbsoluteLocalPath == null ? null : sourceIndexToAbsoluteLocalPath[index];
}
public interface Resolver {
int resolve(@Nullable VirtualFile sourceFile, @NotNull ObjectIntHashMap<Url> map);
}
@Nullable
public MappingList findMappings(@Nullable VirtualFile sourceFile, @NotNull SourceMap sourceMap, @NotNull Resolver resolver) {
int index = resolver.resolve(sourceFile, canonicalizedSourcesMap);
return index < 0 ? null : sourceMap.sourceIndexToMappings[index];
}
@Nullable
public MappingList findMappings(@NotNull List<Url> sourceUrls, @NotNull SourceMap sourceMap, @Nullable VirtualFile sourceFile) {
for (Url sourceUrl : sourceUrls) {
int index = canonicalizedSourcesMap.get(sourceUrl.trimParameters());
if (index != -1) {
return sourceMap.sourceIndexToMappings[index];
}
}
if (sourceFile != null) {
MappingList mappings = findByFile(sourceMap, sourceFile);
if (mappings != null) {
return mappings;
}
}
return null;
}
@Nullable
private static MappingList getMappingsBySource(@NotNull SourceMap sourceMap, int index) {
return index == -1 ? null : sourceMap.sourceIndexToMappings[index];
}
@Nullable
private MappingList findByFile(@NotNull SourceMap sourceMap, @NotNull VirtualFile sourceFile) {
MappingList mappings = null;
if (absoluteLocalPathToSourceIndex != null && sourceFile.isInLocalFileSystem()) {
mappings = getMappingsBySource(sourceMap, absoluteLocalPathToSourceIndex.get(sourceFile.getPath()));
if (mappings == null) {
String sourceFileCanonicalPath = sourceFile.getCanonicalPath();
if (sourceFileCanonicalPath != null) {
mappings = getMappingsBySource(sourceMap, absoluteLocalPathToSourceIndex.get(sourceFileCanonicalPath));
}
}
}
if (mappings == null) {
int index = canonicalizedSourcesMap.get(Urls.newFromVirtualFile(sourceFile).trimParameters());
if (index != -1) {
return sourceMap.sourceIndexToMappings[index];
}
for (int i = 0; i < canonicalizedSources.length; i++) {
Url url = canonicalizedSources[i];
if (Urls.equalsIgnoreParameters(url, sourceFile)) {
return sourceMap.sourceIndexToMappings[i];
}
VirtualFile canonicalFile = sourceFile.getCanonicalFile();
if (canonicalFile != null && !canonicalFile.equals(sourceFile) && Urls.equalsIgnoreParameters(url, canonicalFile)) {
return sourceMap.sourceIndexToMappings[i];
}
}
}
return mappings;
}
}