blob: c912e57999dd32c8aa5e2da5030d850141d49fa7 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.filters;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* User: Irina.Chernushina
* Date: 8/5/11
* Time: 8:36 PM
*/
public class ExceptionWorker {
@NonNls private static final String AT = "at";
private static final String AT_PREFIX = AT + " ";
private static final String STANDALONE_AT = " " + AT + " ";
private final Project myProject;
private Filter.Result myResult;
private PsiClass[] myClasses = PsiClass.EMPTY_ARRAY;
private PsiFile[] myFiles = PsiFile.EMPTY_ARRAY;
private String myMethod;
private Trinity<TextRange, TextRange, TextRange> myInfo;
private final ExceptionInfoCache myCache;
public ExceptionWorker(@NotNull ExceptionInfoCache cache) {
myProject = cache.getProject();
myCache = cache;
}
public void execute(final String line, final int textEndOffset) {
myResult = null;
myInfo = parseExceptionLine(line);
if (myInfo == null) {
return;
}
myMethod = myInfo.getSecond().substring(line);
final int lparenthIndex = myInfo.third.getStartOffset();
final int rparenthIndex = myInfo.third.getEndOffset();
final String fileAndLine = line.substring(lparenthIndex + 1, rparenthIndex).trim();
final int colonIndex = fileAndLine.lastIndexOf(':');
if (colonIndex < 0) return;
final int lineNumber = getLineNumber(fileAndLine.substring(colonIndex + 1));
if (lineNumber < 0) return;
Pair<PsiClass[], PsiFile[]> pair = myCache.resolveClass(myInfo.first.substring(line).trim());
myClasses = pair.first;
myFiles = pair.second;
if (myFiles.length == 0) {
// try find the file with the required name
//todo[nik] it would be better to use FilenameIndex here to honor the scope by it isn't accessible in Open API
myFiles = PsiShortNamesCache.getInstance(myProject).getFilesByName(fileAndLine.substring(0, colonIndex).trim());
}
if (myFiles.length == 0) return;
/*
IDEADEV-4976: Some scramblers put something like SourceFile mock instead of real class name.
final String filePath = fileAndLine.substring(0, colonIndex).replace('/', File.separatorChar);
final int slashIndex = filePath.lastIndexOf(File.separatorChar);
final String shortFileName = slashIndex < 0 ? filePath : filePath.substring(slashIndex + 1);
if (!file.getName().equalsIgnoreCase(shortFileName)) return null;
*/
final int textStartOffset = textEndOffset - line.length();
final int highlightStartOffset = textStartOffset + lparenthIndex + 1;
final int highlightEndOffset = textStartOffset + rparenthIndex;
ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
List<VirtualFile> virtualFilesInLibraries = new ArrayList<VirtualFile>();
List<VirtualFile> virtualFilesInContent = new ArrayList<VirtualFile>();
for (PsiFile file : myFiles) {
VirtualFile virtualFile = file.getVirtualFile();
if (index.isInContent(virtualFile)) {
virtualFilesInContent.add(virtualFile);
}
else {
virtualFilesInLibraries.add(virtualFile);
}
}
List<VirtualFile> virtualFiles;
TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES);
if (virtualFilesInContent.isEmpty()) {
Color libTextColor = UIUtil.getInactiveTextColor();
attributes = attributes.clone();
attributes.setForegroundColor(libTextColor);
attributes.setEffectColor(libTextColor);
virtualFiles = virtualFilesInLibraries;
}
else {
virtualFiles = virtualFilesInContent;
}
HyperlinkInfo linkInfo = HyperlinkInfoFactory.getInstance().createMultipleFilesHyperlinkInfo(virtualFiles, lineNumber - 1, myProject);
myResult = new Filter.Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes);
}
private static int getLineNumber(String lineString) {
// some quick checks to avoid costly exceptions
if (lineString.isEmpty() || lineString.length() > 9 || !Character.isDigit(lineString.charAt(0))) {
return -1;
}
try {
return Integer.parseInt(lineString);
}
catch (NumberFormatException e) {
return -1;
}
}
public Filter.Result getResult() {
return myResult;
}
public PsiClass getPsiClass() {
return ArrayUtil.getFirstElement(myClasses);
}
public String getMethod() {
return myMethod;
}
public PsiFile getFile() {
return ArrayUtil.getFirstElement(myFiles);
}
public Trinity<TextRange, TextRange, TextRange> getInfo() {
return myInfo;
}
//todo [roma] regexp
@Nullable
static Trinity<TextRange, TextRange, TextRange> parseExceptionLine(final String line) {
int startIdx;
if (line.startsWith(AT_PREFIX)){
startIdx = 0;
}
else{
startIdx = line.indexOf(STANDALONE_AT);
if (startIdx < 0) {
startIdx = line.indexOf(AT_PREFIX);
}
if (startIdx < 0) {
startIdx = -1;
}
}
final int rParenIdx = line.lastIndexOf(')');
if (rParenIdx < 0) return null;
final int lParenIdx = line.lastIndexOf('(', rParenIdx);
if (lParenIdx < 0) return null;
final int dotIdx = line.lastIndexOf('.', lParenIdx);
if (dotIdx < 0 || dotIdx < startIdx) return null;
// class, method, link
return Trinity.create(new TextRange(startIdx + 1 + (startIdx >= 0 ? AT.length() : 0), handleSpaces(line, dotIdx, -1, true)),
new TextRange(handleSpaces(line, dotIdx + 1, 1, true), handleSpaces(line, lParenIdx + 1, -1, true)),
new TextRange(lParenIdx, rParenIdx));
}
private static int handleSpaces(String line, int pos, int delta, boolean skip) {
int len = line.length();
while (pos >= 0 && pos < len) {
final char c = line.charAt(pos);
if (skip != Character.isSpaceChar(c)) break;
pos += delta;
}
return pos;
}
}