View Javadoc

1   package com.atlassian.theplugin.idea.bamboo.build;
2   
3   import com.atlassian.theplugin.commons.bamboo.BambooServerFacadeImpl;
4   import com.atlassian.theplugin.commons.exception.ServerPasswordNotProvidedException;
5   import com.atlassian.theplugin.commons.remoteapi.RemoteApiException;
6   import com.atlassian.theplugin.idea.IdeaVersionFacade;
7   import com.atlassian.theplugin.idea.bamboo.BambooBuildAdapterIdea;
8   import com.atlassian.theplugin.util.ClassMatcher;
9   import com.atlassian.theplugin.util.CodeNavigationUtil;
10  import com.atlassian.theplugin.util.PluginUtil;
11  import com.intellij.execution.filters.Filter;
12  import com.intellij.execution.filters.OpenFileHyperlinkInfo;
13  import com.intellij.execution.filters.TextConsoleBuilder;
14  import com.intellij.execution.filters.TextConsoleBuilderFactory;
15  import com.intellij.execution.ui.ConsoleView;
16  import com.intellij.execution.ui.ConsoleViewContentType;
17  import com.intellij.openapi.editor.colors.CodeInsightColors;
18  import com.intellij.openapi.editor.colors.EditorColorsManager;
19  import com.intellij.openapi.editor.markup.TextAttributes;
20  import com.intellij.openapi.fileEditor.OpenFileDescriptor;
21  import com.intellij.openapi.progress.ProgressIndicator;
22  import com.intellij.openapi.progress.Task;
23  import com.intellij.openapi.project.Project;
24  import com.intellij.openapi.vfs.VfsUtil;
25  import com.intellij.openapi.vfs.VirtualFile;
26  import com.intellij.openapi.ui.Messages;
27  import com.intellij.psi.PsiClass;
28  import com.intellij.psi.PsiFile;
29  import com.intellij.psi.PsiMethod;
30  import org.jetbrains.annotations.NotNull;
31  import org.jetbrains.annotations.Nullable;
32  
33  import javax.swing.*;
34  import java.awt.*;
35  import java.awt.event.ActionEvent;
36  import java.awt.event.ActionListener;
37  import java.util.regex.Matcher;
38  import java.util.regex.Pattern;
39  
40  /**
41   * User: jgorycki
42   * Date: Jan 9, 2009
43   * Time: 12:59:25 PM
44   */
45  public class BuildLogPanel extends JPanel implements ActionListener {
46  	private final Project project;
47  
48  	public BuildLogPanel(Project project, BambooBuildAdapterIdea build) {
49  		this.project = project;
50  		setLayout(new BorderLayout());
51  		final ConsoleView console = setupConsole();
52  		add(console.getComponent(), BorderLayout.CENTER);
53  		console.clear();
54  		fetchAndShowBuildLog(build, console);
55  	}
56  
57  	private ConsoleView setupConsole() {
58  		ConsoleView console;
59  		TextConsoleBuilderFactory factory = TextConsoleBuilderFactory.getInstance();
60  		TextConsoleBuilder builder = factory.createBuilder(project);
61  		builder.addFilter(new JavaFileFilter(project));
62  		builder.addFilter(new UnitTestFilter(project));
63  		builder.addFilter(new LoggerFilter());
64  		console = builder.getConsole();
65  		return console;
66  	}
67  
68  	public void fetchAndShowBuildLog(final BambooBuildAdapterIdea build, final ConsoleView consoleView) {
69  		if (build.isValid() == false) {
70  			Messages.showErrorDialog(project, "Cannot fetch build log for invalid build (without the number)!",
71  					PluginUtil.PRODUCT_NAME);
72  			return;
73  		}
74  
75  		consoleView.clear();
76  		consoleView.print("Fetching Bamboo Build Log from the server...", ConsoleViewContentType.NORMAL_OUTPUT);
77  
78  		Task.Backgroundable buildLogTask = new Task.Backgroundable(project, "Retrieving Build Log", false) {
79  			@Override
80  			public void run(@NotNull final ProgressIndicator indicator) {
81  				try {
82  					final String log = BambooServerFacadeImpl.getInstance(PluginUtil.getLogger())
83  							.getBuildLogs(build.getServer(), build.getPlanKey(), build.getNumber());
84  					SwingUtilities.invokeLater(new Runnable() {
85  						public void run() {
86  							consoleView.clear();
87  							consoleView.print(log, ConsoleViewContentType.NORMAL_OUTPUT);
88  						}
89  					});
90  				} catch (ServerPasswordNotProvidedException e) {
91  					showError(consoleView, e);
92  				} catch (RemoteApiException e) {
93  					showError(consoleView, e);
94  				}
95  			}
96  		};
97  		buildLogTask.queue();
98  	}
99  
100 	private void showError(final ConsoleView consoleView, final Exception e) {
101 		SwingUtilities.invokeLater(new Runnable() {
102 			public void run() {
103 				consoleView.clear();
104 				if (e.getMessage().toUpperCase().startsWith("HTTP 404")) {
105 					consoleView.print("No build log available", ConsoleViewContentType.ERROR_OUTPUT);
106 				} else {
107 					consoleView.print(e.getMessage(), ConsoleViewContentType.ERROR_OUTPUT);
108 				}
109 			}
110 		});
111 	}
112 
113 	public void actionPerformed(ActionEvent e) {
114 		// ignore
115 	}
116 
117 	public static class JavaFileFilter implements Filter {
118 
119 		private final TextAttributes hyperlinkAttributes = EditorColorsManager.getInstance().getGlobalScheme()
120 				.getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES);
121 
122 		private final Project project;
123 		public static final int ROW_GROUP = 4;
124 		public static final int COLUMN_GROUP = 6;
125 		public static final int FILENAME_GROUP = 2;
126 		public static final int FULLPATH_GROUP = 1;
127 
128 		public JavaFileFilter(final Project project) {
129 			this.project = project;
130 		}
131 
132 		// match pattern like:
133 		// /path/to/file/MyClass.java:[10,20]
134 		// /path/to/file/MyClass.java
135 		// /path/to/file/MyClass.java:20:30
136 		// /path/to/file/MyClass.java:20
137 
138 		// this pattern is too heavy and it does not allow white characters other than space in the path or file name
139 //		private static final Pattern JAVA_FILE_PATTERN
140 //				= Pattern.compile("([/\\\\]?[\\S ]*?([^/\\\\]+\\.java))(:\\[?(\\d+)([\\,:](\\d+)\\]?)?)?");
141 
142 		// this pattern is more lightwave (for some real data it operates 100 times faster then above one - see UnitTest)
143 		// this pattern also allows white characters other than space in the path/filename (e.g. tab which is allowed by Linux)
144 		// both patterns will not work in case there is a slash or a backslash in the filename 
145 		private static final Pattern JAVA_FILE_PATTERN
146 				= Pattern.compile("^(.*?([^/\\\\]+\\.java))(:\\[?(\\d+)([\\,:](\\d+)\\]?)?)?");
147 
148 		@Nullable
149 		public Result applyFilter(final String line, final int textEndOffset) {
150 
151 //			long start = System.currentTimeMillis();
152 
153 			if (!line.contains(".java")) {
154 				return null; // to make it faster
155 			}
156 
157 			Result ret = null;
158 
159 			final Matcher m = findMatchings(line);
160 			while (m.find()) {
161 				final String matchedString = m.group();
162 
163 //	System.out.println("Matched: " + matchedString);
164 
165 				final String filename = m.group(FILENAME_GROUP);
166 
167 				if (filename != null && filename.length() > 0) {
168 
169 //	System.out.println("Filename: " + filename);
170 
171 //	System.out.println("Path: " +  m.group(FULLPATH_GROUP));
172 
173 					final PsiFile psiFile = CodeNavigationUtil.guessCorrespondingPsiFile(
174 							project, m.group(FULLPATH_GROUP));
175 					if (psiFile != null) {
176 						VirtualFile virtualFile = psiFile.getVirtualFile();
177 						if (virtualFile != null) {
178 
179 							int focusLine = 0;
180 							int focusColumn = 0;
181 							final String rowGroup = m.group(ROW_GROUP);
182 							final String columnGroup = m.group(COLUMN_GROUP);
183 							try {
184 								if (rowGroup != null) {
185 									focusLine = Integer.parseInt(rowGroup) - 1;
186 
187 //	System.out.println("Focus line: " + focusLine);
188 								}
189 								if (columnGroup != null) {
190 									focusColumn = Integer.parseInt(columnGroup) - 1;
191 
192 //	System.out.println("Focus column: " + focusColumn);
193 								}
194 							} catch (NumberFormatException e) {
195 								// just iterate to the next thing
196 							}
197 							final OpenFileHyperlinkInfo info = new OpenFileHyperlinkInfo(project, virtualFile,
198 									focusLine, focusColumn);
199 							final String relativePath = VfsUtil.getPath(project.getBaseDir(), virtualFile, '/');
200 
201 //	System.out.println("Relative path: " + relativePath);
202 
203 							final int startMatchingFileIndex = relativePath != null
204 									? matchedString.replace('\\', '/').indexOf(relativePath)
205 									: matchedString.lastIndexOf(filename);
206 
207 //	System.out.println("Matching file index: " + startMatchingFileIndex);
208 
209 							final int highlightStartOffset =
210 									textEndOffset - line.length() + m.start() + startMatchingFileIndex;
211 
212 //	System.out.println("Highlight start: " + highlightStartOffset);
213 
214 							final int highlightEndOffset = textEndOffset - line.length() + m.end();
215 
216 //	System.out.println("Highliht end: " + highlightEndOffset);
217 
218 							ret = new Result(highlightStartOffset, highlightEndOffset, info, hyperlinkAttributes);
219 						}
220 					}
221 				}
222 			}
223 
224 //			long stop = System.currentTimeMillis(); // stop timing
225 //
226 //			System.out.println((stop - start) + "\t" + line); // print execution time
227 
228 			return ret;
229 		}
230 
231 		public static Matcher findMatchings(final String line) {
232 			return JAVA_FILE_PATTERN.matcher(line);
233 		}
234 	}
235 
236 	public static class UnitTestFilter implements Filter {
237 		private final Project project;
238 
239 		private static final TextAttributes HYPERLINK_ATTRIBUTES = EditorColorsManager.getInstance().getGlobalScheme()
240 				.getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES);
241 
242 		public UnitTestFilter(@NotNull final Project project) {
243 			this.project = project;
244 		}
245 
246 		@Nullable
247 		public Result applyFilter(final String line, final int textEndOffset) {
248 			for (ClassMatcher.MatchInfo match : ClassMatcher.find(line)) {
249 				final Result res = handleMatch(line, textEndOffset, match);
250 				if (res != null) {
251 					return res;
252 				}
253 			}
254 			return null;
255 		}
256 
257 		private Result handleMatch(final String line, final int textEndOffset, final ClassMatcher.MatchInfo match) {
258 			PsiClass aClass = IdeaVersionFacade.getInstance().findClass(match.getMatch(), project);
259 			if (aClass == null) {
260 				return null;
261 			}
262 			final PsiFile file = (PsiFile) aClass.getContainingFile().getNavigationElement();
263 			if (file == null) {
264 				return null;
265 			}
266 
267 
268 			int highlightStartOffset = textEndOffset - line.length() + match.getIndex();
269 			final int highlightEndOffset = highlightStartOffset + match.getMatch().length();
270 
271 			VirtualFile virtualFile = file.getVirtualFile();
272 			if (virtualFile == null) {
273 				return null;
274 			}
275 
276 			int targetLine = 0;
277 			// special handling of sure-fire report from Maven for JUnit 3 tests: method_name(FQCN)
278 			if (match.getIndex() > 0 && line.charAt(match.getIndex() - 1) == '('
279 					&& (match.getIndex() + match.getMatch().length() < line.length()
280 					&& line.charAt(match.getIndex() + match.getMatch().length()) == ')')) {
281 				// trying to extract method name which should be just before the '('
282 				int i = match.getIndex() - 2;
283 				for (; i >= 0; i--) {
284 					char currentChar = line.charAt(i);
285 					if (Character.isWhitespace(currentChar)) {
286 						break;
287 					}
288 				}
289 				if (i != match.getIndex() - 2) {
290 					final String methodName = line.substring(i + 1, match.getIndex() - 1);
291 
292 					// means we found some potential method
293 					PsiMethod[] methods = aClass.findMethodsByName(methodName, true);
294 					if (methods != null && methods.length != 0 && methods[0] != null) {
295 						highlightStartOffset = textEndOffset - line.length() + i + 1;
296 						final OpenFileDescriptor openFileDescriptor =
297 								new OpenFileDescriptor(project, virtualFile, methods[0].getTextOffset());
298 						final OpenFileHyperlinkInfo info = new OpenFileHyperlinkInfo(openFileDescriptor);
299 						return new Result(highlightStartOffset, highlightEndOffset, info, HYPERLINK_ATTRIBUTES);
300 					}
301 				}
302 			}
303 
304 			final OpenFileHyperlinkInfo info = new OpenFileHyperlinkInfo(project, virtualFile, targetLine);
305 			return new Result(highlightStartOffset, highlightEndOffset, info, HYPERLINK_ATTRIBUTES);
306 		}
307 	}
308 
309 
310 	public static class LoggerFilter implements Filter {
311 		private static final Color DARK_GREEN = new Color(0, 128, 0);
312 		private static final TextAttributes ERROR_TEXT_ATTRIBUTES = new TextAttributes();
313 		private static final TextAttributes INFO_TEXT_ATTRIBUTES = new TextAttributes();
314 		private static final TextAttributes WARNING_TEXT_ATTRIBUTES = new TextAttributes();
315 
316 		static {
317 			ERROR_TEXT_ATTRIBUTES.setForegroundColor(Color.RED);
318 			INFO_TEXT_ATTRIBUTES.setForegroundColor(DARK_GREEN);
319 			//CHECKSTYLE:MAGIC:OFF
320 			WARNING_TEXT_ATTRIBUTES.setForegroundColor(new Color(185, 150, 0));
321 			//CHECKSTYLE:MAGIC:ON
322 		}
323 
324 		public LoggerFilter() {
325 		}
326 
327 		public Result applyFilter(final String line, final int textEndOffset) {
328 
329 			if (line.indexOf("\t[INFO]") != -1 || line.indexOf("\tINFO") != -1) {
330 				final int highlightStartOffset = textEndOffset - line.length();
331 				return new Result(highlightStartOffset, textEndOffset, null, INFO_TEXT_ATTRIBUTES);
332 			} else if (line.indexOf("\t[ERROR]") != -1 || line.indexOf("\tERROR") != -1) {
333 				final int highlightStartOffset = textEndOffset - line.length();
334 				return new Result(highlightStartOffset, textEndOffset, null, ERROR_TEXT_ATTRIBUTES);
335 			} else if (line.indexOf("\t[WARNING]") != -1 || line.indexOf("\tWARNING") != -1) {
336 				final int highlightStartOffset = textEndOffset - line.length();
337 				return new Result(highlightStartOffset, textEndOffset, null, WARNING_TEXT_ATTRIBUTES);
338 			}
339 			return null;
340 		}
341 	}
342 }