[console filter] inline bomb with 100 ms
[idea/community.git] / platform / lang-api / src / com / intellij / execution / filters / RegexpFilter.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.filters;
17
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.openapi.vfs.LocalFileSystem;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import org.jetbrains.annotations.NonNls;
23 import org.jetbrains.annotations.Nullable;
24
25 import java.io.File;
26 import java.util.TreeMap;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 /**
31  * @author Yura Cangea
32  * @version 1.0
33  */
34 public class RegexpFilter implements Filter {
35   @NonNls public static final String FILE_PATH_MACROS = "$FILE_PATH$";
36   @NonNls public static final String LINE_MACROS = "$LINE$";
37   @NonNls public static final String COLUMN_MACROS = "$COLUMN$";
38
39   @NonNls private static final String FILE_PATH_REGEXP = "((?:\\p{Alpha}\\:)?[0-9 a-z_A-Z\\-\\\\./]+)";
40   @NonNls private static final String NUMBER_REGEXP = "([0-9]+)";
41   @NonNls private static final String FILE_STR = "file";
42   @NonNls private static final String LINE_STR = "line";
43   @NonNls private static final String COLUMN_STR = "column";
44
45   private final int myFileRegister;
46   private final int myLineRegister;
47   private final int myColumnRegister;
48
49   private final Pattern myPattern;
50   private final Project myProject;
51
52   public RegexpFilter(Project project, @NonNls String expression) {
53     myProject = project;
54     validate(expression);
55
56     if (expression.trim().isEmpty()) {
57       throw new InvalidExpressionException("expression == null or empty");
58     }
59
60     int filePathIndex = expression.indexOf(FILE_PATH_MACROS);
61     int lineIndex = expression.indexOf(LINE_MACROS);
62     int columnIndex = expression.indexOf(COLUMN_MACROS);
63
64     if (filePathIndex == -1) {
65       throw new InvalidExpressionException("Expression must contain " + FILE_PATH_MACROS + " macros.");
66     }
67
68     final TreeMap<Integer,String> map = new TreeMap<Integer, String>();
69
70     map.put(new Integer(filePathIndex), FILE_STR);
71
72     expression = StringUtil.replace(expression, FILE_PATH_MACROS, FILE_PATH_REGEXP);
73
74     if (lineIndex != -1) {
75       expression = StringUtil.replace(expression, LINE_MACROS, NUMBER_REGEXP);
76       map.put(new Integer(lineIndex), LINE_STR);
77     }
78
79     if (columnIndex != -1) {
80       expression = StringUtil.replace(expression, COLUMN_MACROS, NUMBER_REGEXP);
81       map.put(new Integer(columnIndex), COLUMN_STR);
82     }
83
84     // The block below determines the registers based on the sorted map.
85     int count = 0;
86     for (final Integer integer : map.keySet()) {
87       count++;
88       final String s = map.get(integer);
89
90       if (FILE_STR.equals(s)) {
91         filePathIndex = count;
92       }
93       else if (LINE_STR.equals(s)) {
94         lineIndex = count;
95       }
96       else if (COLUMN_STR.equals(s)) {
97         columnIndex = count;
98       }
99     }
100
101     myFileRegister = filePathIndex;
102     myLineRegister = lineIndex;
103     myColumnRegister = columnIndex;
104     myPattern = Pattern.compile(expression, Pattern.MULTILINE);
105   }
106
107   @SuppressWarnings("ResultOfMethodCallIgnored")
108   public static void validate(String expression) {
109     if (StringUtil.isEmptyOrSpaces(expression)) throw new InvalidExpressionException("expression == null or empty");
110
111     expression = substituteMacrosWithRegexps(expression);
112     Pattern.compile(expression, Pattern.MULTILINE);
113   }
114
115   public Pattern getPattern() {
116     return myPattern;
117   }
118
119   private static String substituteMacrosWithRegexps(String expression) {
120     int filePathIndex = expression.indexOf(FILE_PATH_MACROS);
121     int lineIndex = expression.indexOf(LINE_MACROS);
122     int columnIndex = expression.indexOf(COLUMN_MACROS);
123
124     if (filePathIndex == -1) {
125       throw new InvalidExpressionException("Expression must contain " + FILE_PATH_MACROS + " macros.");
126     }
127
128     expression = StringUtil.replace(expression, FILE_PATH_MACROS, FILE_PATH_REGEXP);
129
130     if (lineIndex != -1) {
131       expression = StringUtil.replace(expression, LINE_MACROS, NUMBER_REGEXP);
132     }
133
134     if (columnIndex != -1) {
135       expression = StringUtil.replace(expression, COLUMN_MACROS, NUMBER_REGEXP);
136     }
137     return expression;
138   }
139
140   @Override
141   public Result applyFilter(String line, int entireLength) {
142     Matcher matcher = myPattern.matcher(StringUtil.newBombedCharSequence(line, 100));
143     if (!matcher.find()) {
144       return null;
145     }
146
147     String filePath = matcher.group(myFileRegister);
148     if (filePath == null) {
149       return null;
150     }
151
152     String lineNumber = "0";
153     if (myLineRegister != -1) {
154       lineNumber = matcher.group(myLineRegister);
155     }
156
157     String columnNumber = "0";
158     if (myColumnRegister != -1) {
159       columnNumber = matcher.group(myColumnRegister);
160     }
161
162     int line1 = 0;
163     int column = 0;
164     try {
165       line1 = Integer.parseInt(lineNumber);
166       column = Integer.parseInt(columnNumber);
167     } catch (NumberFormatException e) {
168       // Do nothing, so that line and column will remain at their initial zero values.
169     }
170
171     if (line1 > 0) line1 -= 1;
172     if (column > 0) column -= 1;
173     // Calculate the offsets relative to the entire text.
174     final int highlightStartOffset = entireLength - line.length() + matcher.start(myFileRegister);
175     final int highlightEndOffset = highlightStartOffset + filePath.length();
176     final HyperlinkInfo info = createOpenFileHyperlink(filePath, line1, column);
177     return new Result(highlightStartOffset, highlightEndOffset, info);
178   }
179
180   @Nullable
181   protected HyperlinkInfo createOpenFileHyperlink(String fileName, final int line, final int column) {
182     fileName = fileName.replace(File.separatorChar, '/');
183     VirtualFile file = LocalFileSystem.getInstance().findFileByPath(fileName);
184     return file != null ? new OpenFileHyperlinkInfo(myProject, file, line, column) : null;
185   }
186
187   public static String[] getMacrosName() {
188     return new String[] {FILE_PATH_MACROS, LINE_MACROS, COLUMN_MACROS};
189   }
190 }