| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.netbeans.modules.javascript.jstestdriver; |
| |
| import org.netbeans.modules.javascript.jstestdriver.ui.nodes.JSTestDriverTestRunnerNodeFactory; |
| import org.netbeans.modules.javascript.jstestdriver.ui.nodes.JumpToCallStackAction; |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.MalformedURLException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.Semaphore; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.ChangeListener; |
| import org.netbeans.api.extexecution.print.ConvertedLine; |
| import org.netbeans.api.extexecution.print.LineConvertor; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.libs.jstestdriver.api.JsTestDriver; |
| import org.netbeans.libs.jstestdriver.api.ServerListener; |
| import org.netbeans.libs.jstestdriver.api.TestListener; |
| import org.netbeans.modules.gsf.testrunner.ui.api.Manager; |
| import org.netbeans.modules.gsf.testrunner.api.Report; |
| import org.netbeans.modules.gsf.testrunner.api.RerunHandler; |
| import org.netbeans.modules.gsf.testrunner.api.RerunType; |
| import org.netbeans.modules.gsf.testrunner.api.Status; |
| import org.netbeans.modules.gsf.testrunner.api.TestSession; |
| import org.netbeans.modules.gsf.testrunner.api.TestSuite; |
| import org.netbeans.modules.gsf.testrunner.api.Testcase; |
| import org.netbeans.modules.gsf.testrunner.api.Trouble; |
| import org.netbeans.modules.web.browser.api.WebBrowser; |
| import org.netbeans.modules.web.browser.api.WebBrowserFeatures; |
| import org.netbeans.modules.web.browser.api.WebBrowserPane; |
| import org.netbeans.modules.web.common.api.RemoteFileCache; |
| import org.netbeans.modules.web.common.api.ServerURLMapping; |
| import org.netbeans.modules.web.common.api.WebUtils; |
| import org.openide.LifecycleManager; |
| import org.openide.cookies.LineCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.filesystems.MIMEResolver; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.DataObjectNotFoundException; |
| import org.openide.text.Line; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.Pair; |
| import org.openide.util.Parameters; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.lookup.AbstractLookup; |
| import org.openide.util.lookup.InstanceContent; |
| import org.openide.windows.OutputEvent; |
| import org.openide.windows.OutputListener; |
| |
| @Messages("JsTestDriverResolver=js-test-driver Conf Files") |
| @MIMEResolver.Registration( |
| displayName="#JsTestDriverResolver", |
| position=132, // has to be after languages.apachecon resolver, see #267416 |
| resource="resources/mime-resolver.xml" |
| ) |
| public class JSTestDriverSupport { |
| |
| private static JSTestDriverSupport def; |
| private static final Logger LOGGER = Logger.getLogger(JSTestDriverSupport.class.getName()); |
| private static final Logger USG_LOGGER = Logger.getLogger("org.netbeans.ui.metrics.jstestdriver"); // NOI18N |
| private RequestProcessor RP = new RequestProcessor("js-test-driver server", 5); //NOI18N |
| private AbstractLookup projectContext; |
| private InstanceContent lookupContent; |
| private List<WebBrowserPane> integratedBrowserPanes; |
| |
| public static synchronized JSTestDriverSupport getDefault() { |
| if (def == null) { |
| def = new JSTestDriverSupport(); |
| } |
| return def; |
| } |
| |
| private JsTestDriver testDriver; |
| private volatile boolean starting = false; |
| |
| private JSTestDriverSupport() { |
| lookupContent = new InstanceContent(); |
| projectContext = new AbstractLookup(lookupContent); |
| } |
| |
| private synchronized JsTestDriver getJsTestDriver() { |
| if (testDriver == null) { |
| if (!isConfiguredProperly()) { |
| return null; |
| } |
| String jsTestDriverJar = JSTestDriverCustomizerPanel.getJSTestDriverJar(); |
| File f = new File(jsTestDriverJar); |
| try { |
| testDriver = new JsTestDriver(f); |
| } catch (RuntimeException ex) { |
| LOGGER.log(Level.INFO, "cannot access js-test-driver wrapper", ex); //NOI18N |
| return null; |
| } |
| } |
| return testDriver; |
| } |
| |
| |
| public String getUserDescription() { |
| if (JSTestDriverCustomizerPanel.getPort() == -1) { |
| return NbBundle.getMessage(JSTestDriverSupport.class, "SERVER_EXTERNAL", JSTestDriverCustomizerPanel.getServerURL()); |
| } else if (wasStartedExternally()) { |
| return NbBundle.getMessage(JSTestDriverSupport.class, "SERVER_EXTERNAL2", JSTestDriverCustomizerPanel.getPort()); |
| } else if (isRunning()) { |
| return NbBundle.getMessage(JSTestDriverSupport.class, "SERVER_RUNNING", JSTestDriverCustomizerPanel.getServerURL()); |
| } else { |
| return NbBundle.getMessage(JSTestDriverSupport.class, "SERVER_NOT_RUNNING"); |
| } |
| } |
| |
| boolean isRunning() { |
| return testDriver != null && testDriver.isRunning(); |
| } |
| |
| void forgetCurrentServer() { |
| testDriver = null; |
| } |
| |
| public boolean wasStartedExternally() { |
| return testDriver != null && testDriver.wasStartedExternally(); |
| } |
| |
| public boolean isStarting() { |
| return starting; |
| } |
| |
| public void stop() { |
| assert isRunning(); |
| assert testDriver != null; |
| testDriver.stopServer(); |
| TestDriverServiceNode.getInstance().refresh(); |
| try { // try waiting a bit after stopping server |
| Thread.sleep(1000); |
| } catch (InterruptedException ex) { |
| LOGGER.log(Level.INFO, null, ex); |
| Thread.currentThread().interrupt(); |
| } |
| if (integratedBrowserPanes != null) { |
| for (WebBrowserPane wbp : integratedBrowserPanes) { |
| wbp.close(true); |
| } |
| } |
| } |
| |
| public void start(final ServerListener l) { |
| assert !isRunning(); |
| JsTestDriver td = getJsTestDriver(); |
| if (td == null) { |
| if (configure()) { |
| td = getJsTestDriver(); |
| } |
| } |
| if (td == null) { |
| return; |
| } |
| final JsTestDriver td2 = td; |
| starting = true; |
| TestDriverServiceNode.getInstance().refresh(); |
| RP.post(new Runnable() { |
| |
| @Override |
| public void run() { |
| try { |
| td2.startServer(JSTestDriverCustomizerPanel.getPort(), |
| JSTestDriverCustomizerPanel.isStricModel(), |
| new ServerListener() { |
| |
| @Override |
| public void serverStarted() { |
| RP.post(new Runnable() { |
| @Override |
| public void run() { |
| captureBrowsers(); |
| if (l != null) { |
| l.serverStarted(); |
| } |
| starting = false; |
| TestDriverServiceNode.getInstance().refresh(); |
| logUsage(JSTestDriverSupport.class, "USG_JSTESTDRIVER_STARTED", null); // NOI18N |
| } |
| }); |
| } |
| |
| }); |
| if (td2.wasStartedExternally()) { |
| starting = false; |
| TestDriverServiceNode.getInstance().refresh(); |
| if (l != null) { |
| l.serverStarted(); |
| } |
| } |
| } catch (Throwable t) { |
| LOGGER.log(Level.SEVERE, "cannot start server", t); //NOI18N |
| } |
| } |
| }); |
| } |
| |
| public boolean configure() { |
| final boolean[] res = new boolean[1]; |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| boolean b = JSTestDriverCustomizerPanel.showCustomizer(); |
| TestDriverServiceNode.getInstance().refresh(); |
| res[0] = b; |
| } |
| }; |
| try { |
| if (SwingUtilities.isEventDispatchThread()) { |
| r.run(); |
| } else { |
| // it should be safe to call invokeAndWait here |
| // as this action will be either called from AWT thread or from |
| // RequestProcessor as a result of "test project" or "start server" in |
| // case when configuration is missing the first time: |
| SwingUtilities.invokeAndWait(r); |
| } |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (InvocationTargetException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return res[0]; |
| } |
| |
| public boolean isConfiguredProperly() { |
| return JSTestDriverCustomizerPanel.isConfiguredProperly(); |
| } |
| |
| public void runAllTests(Project project, File baseFolder, File configFile, |
| String testsToRun) { |
| JsTestDriver td = getJsTestDriver(); |
| if (td == null) { |
| if (configure()) { |
| td = getJsTestDriver(); |
| } |
| } |
| if (td == null) { |
| return; |
| } |
| String serverURL = JSTestDriverCustomizerPanel.getServerURL(); |
| int port = JSTestDriverCustomizerPanel.getPort(); |
| boolean strictMode = JSTestDriverCustomizerPanel.isStricModel(); |
| if (!isRunning() && port != -1) { |
| final Semaphore s = new Semaphore(0); |
| start(new ServerListener() { |
| @Override |
| public void serverStarted() { |
| try { |
| // give browsers some time to start and connect to server |
| Thread.sleep(3000); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| s.release(); |
| } |
| }); |
| try { |
| s.acquire(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| if (!isRunning()) { |
| return; |
| } |
| } |
| updateJsDebuggerProjectContext(project); |
| TestListener listener = new Listener(project, new Rerun(project, baseFolder, configFile)); |
| LineConvertor convertor = new LineConvertorImpl(project); |
| td.runTests(serverURL, strictMode, baseFolder, configFile, testsToRun, listener, convertor); |
| } |
| |
| private void updateJsDebuggerProjectContext(Project p) { |
| // update lookup used by JS debugger with the right project |
| lookupContent.set(Collections.singletonList(p), null); |
| } |
| |
| private void captureBrowsers() { |
| integratedBrowserPanes = new ArrayList<WebBrowserPane>(); |
| for (WebBrowser bd : JSTestDriverCustomizerPanel.getBrowsers()) { |
| String s = JSTestDriverCustomizerPanel.getServerURL(); |
| // #230400 - use IP address instead of localhost so that mobile browsers testing works: |
| if (bd.getBrowserFamily().isMobile() && s.startsWith("http://localhost:")) { |
| s = s.replaceAll("localhost", WebUtils.getLocalhostInetAddress().getHostAddress()); |
| } |
| s = s+"/capture"; //NOI18N |
| if (bd.hasNetBeansIntegration()) { |
| // '/timeout/-1/' - will prevent js-test-driver from timeouting the test |
| // when test execution takes too much time, for example when test is being debugged |
| s += "/timeout/-1/"; //NOI18N |
| } |
| if (JSTestDriverCustomizerPanel.isStricModel()) { |
| s += "?strict"; //NOI18N |
| } |
| try { |
| URL u = new URL(s); |
| WebBrowserFeatures features = new WebBrowserFeatures(true, true, false, false, true, false); |
| WebBrowserPane pane = bd.createNewBrowserPane(features, true); |
| // the problem here is following: js-test-driver is a global server |
| // which does not have any project specific context. But in order to |
| // debug JavaScript the JS debugger needs a project context in order |
| // to correctly resolve breakpoints etc. So when server is started |
| // there will not be any project context; only when a test is |
| // executed from a project the project context will be set for JS debugger by |
| // updating the lookup; JS debugger listens on lookup changes |
| pane.setProjectContext(projectContext); |
| pane.showURL(u); |
| if (bd.hasNetBeansIntegration()) { |
| integratedBrowserPanes.add(pane); |
| } |
| } catch (MalformedURLException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| |
| private static class LineConvertorImpl implements LineConvertor { |
| |
| private Project p; |
| |
| public LineConvertorImpl(Project p) { |
| this.p = p; |
| } |
| |
| // XXX: exact this algorithm is also in |
| // org.netbeans.modules.web.webkit.tooling.console.BrowserConsoleLogger.java |
| // keep them in sync |
| @Override |
| public List<ConvertedLine> convert(String line) { |
| // pattern is "at ...... (file:line:column)" |
| // file can be also http:// url |
| if (!line.endsWith(")")) { //NOI18N |
| return convertLineURL(line); |
| } |
| int start = line.lastIndexOf('('); |
| if (start == -1) { |
| return null; |
| } |
| int lineNumberEnd = line.lastIndexOf(':'); |
| if (lineNumberEnd == -1) { |
| return null; |
| } |
| int fileEnd = line.lastIndexOf(':', lineNumberEnd-1); |
| if (fileEnd == -1) { |
| return null; |
| } |
| if (start >= fileEnd) { |
| return null; |
| } |
| int lineNumber = -1; |
| int columnNumber = -1; |
| try { |
| lineNumber = Integer.parseInt(line.substring(fileEnd+1, lineNumberEnd)); |
| columnNumber = Integer.parseInt(line.substring(lineNumberEnd+1, line.length()-1)); |
| } catch (NumberFormatException e) { |
| //ignore |
| } |
| if (columnNumber != -1 && lineNumber == -1) { |
| // perhaps stack trace had only line number: |
| lineNumber = columnNumber; |
| } |
| if (lineNumber == -1) { |
| return null; |
| } |
| String file = line.substring(start+1, fileEnd); |
| if (file.length() == 0) { |
| return null; |
| } |
| FileObject fo = p.getProjectDirectory().getFileObject(file); |
| if (fo == null) { |
| return null; |
| } |
| List<ConvertedLine> res = new ArrayList<ConvertedLine>(); |
| //res.add(ConvertedLine.forText(line.substring(0, start), null)); |
| ListenerImpl l = new ListenerImpl(fo, lineNumber, columnNumber); |
| res.add(ConvertedLine.forText(/*line.substring(start, line.length()-1)*/line, l.isValidHyperlink() ? l : null)); |
| //res.add(ConvertedLine.forText(line.substring(line.length()-1, line.length()), null)); |
| return res; |
| } |
| |
| private List<ConvertedLine> convertLineURL(String line) { |
| int u1 = line.indexOf("http://"); // NOI18N |
| if (u1 < 0) { |
| u1 = line.indexOf("https://"); // NOI18N |
| } |
| if (u1 < 0) { |
| return null; |
| } |
| int ue = line.indexOf(' ', u1); |
| int uee = line.indexOf('\n', u1); |
| if (uee != -1 |
| && uee < ue) { |
| // #236037 |
| ue = uee; |
| } |
| if (ue < 0) { |
| ue = line.length(); |
| } |
| int col2 = line.lastIndexOf(':', ue); |
| if (col2 < 0) { |
| return null; |
| } |
| int col1 = line.lastIndexOf(':', col2 - 1); |
| if (col1 < 0) { |
| return null; |
| } |
| int lineNumber = -1; |
| int columnNumber = -1; |
| try { |
| lineNumber = Integer.parseInt(line.substring(col1+1, col2)); |
| columnNumber = Integer.parseInt(line.substring(col2+1, ue)); |
| } catch (NumberFormatException e) { |
| //ignore |
| } |
| if (columnNumber != -1 && lineNumber == -1) { |
| // perhaps stack trace had only line number: |
| lineNumber = columnNumber; |
| } |
| if (lineNumber == -1) { |
| return null; |
| } |
| String file = line.substring(u1, col1); |
| if (file.length() == 0) { |
| return null; |
| } |
| |
| FileObject fo = null; |
| try { |
| URL url = URI.create(file).toURL(); |
| fo = ServerURLMapping.fromServer(p, url); |
| if (fo == null) { |
| fo = RemoteFileCache.getRemoteFile(url); |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| if (fo == null) { |
| return null; |
| } |
| List<ConvertedLine> res = new ArrayList<ConvertedLine>(); |
| //res.add(ConvertedLine.forText(line.substring(0, start), null)); |
| ListenerImpl l = new ListenerImpl(fo, lineNumber, columnNumber); |
| res.add(ConvertedLine.forText(/*line.substring(start, line.length()-1)*/line, l.isValidHyperlink() ? l : null)); |
| //res.add(ConvertedLine.forText(line.substring(line.length()-1, line.length()), null)); |
| return res; |
| } |
| |
| } |
| |
| private static class ListenerImpl implements OutputListener { |
| |
| private FileObject fo; |
| private int line; |
| private int column; |
| |
| public ListenerImpl(FileObject fo, int line, int column) { |
| this.fo = fo; |
| this.line = line; |
| this.column = column; |
| } |
| |
| @Override |
| public void outputLineSelected(OutputEvent ev) { |
| } |
| |
| @Override |
| public void outputLineAction(OutputEvent ev) { |
| Line l = getLine(); |
| if (l != null) { |
| l.show(Line.ShowOpenType.OPEN, |
| Line.ShowVisibilityType.FOCUS, column != -1 ? column -1 : -1); |
| } |
| } |
| |
| private Line getLine() { |
| LineCookie result = null; |
| try { |
| DataObject dataObject = DataObject.find(fo); |
| if (dataObject != null) { |
| result = dataObject.getCookie(LineCookie.class); |
| } |
| } catch (DataObjectNotFoundException e) { |
| e.printStackTrace(); |
| } |
| if (result != null) { |
| return result.getLineSet().getCurrent(line-1); |
| } |
| return null; |
| } |
| |
| @Override |
| public void outputLineCleared(OutputEvent ev) { |
| } |
| |
| public boolean isValidHyperlink() { |
| return getLine() != null; |
| } |
| |
| } |
| |
| private static class Listener implements TestListener { |
| |
| private TestSession testSession; |
| private Manager manager; |
| private Report report; |
| |
| |
| @NbBundle.Messages({ |
| "# {0} - project name", |
| "JsTestDriverSupport.runner.title={0} (JS Test Driver)", |
| }) |
| public Listener(Project project, Rerun rerun) { |
| manager = Manager.getInstance(); |
| manager.setNodeFactory(new JSTestDriverTestRunnerNodeFactory(new CallStackCallback(project))); |
| testSession = new TestSession( |
| Bundle.JsTestDriverSupport_runner_title(ProjectUtils.getInformation(project).getDisplayName()), |
| project, TestSession.SessionType.TEST); |
| testSession.setRerunHandler(rerun); |
| manager.testStarted(testSession); |
| } |
| |
| @Override |
| public void onTestComplete(TestResult testResult) { |
| TestSuite currentSuite = testSession.getCurrentSuite(); |
| if (currentSuite == null || !currentSuite.getName().equals(testResult.getBrowserInfo().getDisplayName())) { |
| if (report != null) { |
| manager.displayReport(testSession, report, true); |
| } |
| TestSuite ts = new TestSuite(testResult.getBrowserInfo().getDisplayName()); |
| testSession.addSuite(ts); |
| report = testSession.getReport(0); |
| manager.displaySuiteRunning(testSession, ts.getName()); |
| } |
| Testcase testCase = new Testcase(testResult.getTestCaseName()+"."+testResult.getTestName(), null, testSession); //NOI18N |
| testCase.setStatus(convert(testResult.getResult())); |
| testCase.setTimeMillis(testResult.getDuration()); |
| if (testResult.getResult() == TestResult.Result.failed || testResult.getResult() == TestResult.Result.error) { |
| Trouble t = new Trouble(true); |
| if (testResult.getStack().length() > 0) { |
| t.setStackTrace(trimArray(testResult.getStack().split("\\u000d"))); //NOI18N |
| testCase.addOutputLines(Arrays.asList(testResult.getStack().split("\\u000d"))); //NOI18N |
| //manager.displayOutput(testSession, testResult.getStack(), true); |
| } |
| if (testResult.getMessage().length() > 0) { |
| //manager.displayOutput(testSession, testResult.getMessage(), true); |
| } |
| if (testResult.getLog().length() > 0) { |
| //manager.displayOutput(testSession, testResult.getLog(), true); |
| } |
| testCase.setTrouble(t); |
| } |
| testSession.addTestCase(testCase); |
| report.update(testSession.getReport(0)); |
| manager.displayReport(testSession, report, false); |
| } |
| |
| private Status convert(TestResult.Result res) { |
| switch (res) { |
| case passed: |
| return Status.PASSED; |
| case failed: |
| return Status.FAILED; |
| case error: |
| return Status.ERROR; |
| case started: |
| return Status.PENDING; |
| default: |
| throw new AssertionError(res.name()); |
| } |
| } |
| |
| @Override |
| public void onTestingFinished() { |
| manager.sessionFinished(testSession); |
| if (report == null) { |
| // no tests were run; generate empty report: |
| testSession.addSuite(TestSuite.ANONYMOUS_TEST_SUITE); |
| report = testSession.getReport(0); |
| } |
| manager.displayReport(testSession, report, true); |
| } |
| |
| private String[] trimArray(String[] split) { |
| if (split == null) { |
| return null; |
| } |
| List<String> r = new ArrayList<String>(); |
| for (int i = 0; i < split.length; i++) { |
| String s = split[i].trim(); |
| if (s.length() > 0) { |
| r.add(s); |
| } |
| } |
| return r.toArray(new String[r.size()]); |
| } |
| |
| } |
| |
| private static class Rerun implements RerunHandler { |
| |
| private Project project; |
| private File baseFolder; |
| private File configFile; |
| private RequestProcessor RP = new RequestProcessor("js-test-driver rerun", 5); //NOI18N |
| |
| public Rerun(Project project, File baseFolder, File configFile) { |
| this.project = project; |
| this.baseFolder = baseFolder; |
| this.configFile = configFile; |
| } |
| |
| @Override |
| public void rerun() { |
| RP.post(new Runnable() { |
| @Override |
| public void run() { |
| LifecycleManager.getDefault().saveAll(); |
| JSTestDriverSupport.getDefault().runAllTests(project, baseFolder, configFile, "all"); //NOI18N |
| } |
| }); |
| } |
| |
| @Override |
| public void rerun(Set<Testcase> tests) { |
| } |
| |
| @Override |
| public boolean enabled(RerunType type) { |
| return RerunType.ALL.equals(type); |
| } |
| |
| @Override |
| public void addChangeListener(ChangeListener listener) { |
| } |
| |
| @Override |
| public void removeChangeListener(ChangeListener listener) { |
| } |
| |
| } |
| |
| public static void logUsage(Class srcClass, String message, Object[] params) { |
| Parameters.notNull("message", message); // NOI18N |
| |
| LogRecord logRecord = new LogRecord(Level.INFO, message); |
| logRecord.setLoggerName(USG_LOGGER.getName()); |
| logRecord.setResourceBundle(NbBundle.getBundle(srcClass)); |
| logRecord.setResourceBundleName(srcClass.getPackage().getName() + ".Bundle"); // NOI18N |
| if (params != null) { |
| logRecord.setParameters(params); |
| } |
| USG_LOGGER.log(logRecord); |
| } |
| |
| //~ Inner classes |
| |
| private static final class CallStackCallback implements JumpToCallStackAction.Callback { |
| |
| private static final Pattern FILE_LINE_PATTERN = Pattern.compile(" [(](?<FILE>[^:]+):(?<LINE>\\d+):(?<COLUMN>\\d+)[)]"); // NOI18N |
| |
| private final File projectDir; |
| |
| |
| public CallStackCallback(Project project) { |
| assert project != null; |
| projectDir = FileUtil.toFile(project.getProjectDirectory()); |
| } |
| |
| @Override |
| public Pair<File, int[]> parseLocation(String callStack) { |
| Matcher matcher = FILE_LINE_PATTERN.matcher(callStack); |
| if (matcher.find()) { |
| File path = new File(matcher.group("FILE").replace('/', File.separatorChar)); // NOI18N |
| File file; |
| if (path.isAbsolute()) { |
| file = path; |
| } else { |
| file = new File(projectDir, path.getPath()); |
| if (!file.isFile()) { |
| return null; |
| } |
| } |
| int[] lineColumn = {Integer.parseInt(matcher.group("LINE")), Integer.parseInt(matcher.group("COLUMN"))}; |
| return Pair.of(file, lineColumn); // NOI18N |
| } |
| return null; |
| } |
| |
| } |
| |
| } |