blob: 64737c03b4763db414066e3b89c598332a30c742 [file] [log] [blame]
/*
* 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.java.mx.project;
import java.awt.Toolkit;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Future;
import org.netbeans.api.actions.Openable;
import org.netbeans.api.debugger.*;
import org.netbeans.api.debugger.jpda.ListeningDICookie;
import org.netbeans.api.extexecution.ExecutionDescriptor;
import org.netbeans.api.extexecution.ExecutionService;
import org.netbeans.api.extexecution.base.ProcessBuilder;
import org.netbeans.api.extexecution.print.ConvertedLine;
import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.SingleMethod;
import org.openide.LifecycleManager;
import org.openide.cookies.LineCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.text.Line;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.windows.OutputEvent;
import org.openide.windows.OutputListener;
final class SuiteActionProvider implements ActionProvider {
private static final String TEST_MX_PATH = System.getProperty("org.netbeans.modules.java.mx.project.test.mxpath"); // NOI18N
private static final RequestProcessor ASYNC = new RequestProcessor("Mx Async", 10);
private static final List<String> SUPPORTED_ACTIONS = Arrays.asList(
ActionProvider.COMMAND_CLEAN,
ActionProvider.COMMAND_BUILD,
ActionProvider.COMMAND_COMPILE_SINGLE,
ActionProvider.COMMAND_REBUILD,
ActionProvider.COMMAND_TEST_SINGLE,
ActionProvider.COMMAND_RUN_SINGLE,
ActionProvider.COMMAND_DEBUG_TEST_SINGLE,
ActionProvider.COMMAND_DEBUG_SINGLE,
SingleMethod.COMMAND_DEBUG_SINGLE_METHOD,
SingleMethod.COMMAND_RUN_SINGLE_METHOD
);
private final SuiteProject prj;
SuiteActionProvider(SuiteProject prj) {
this.prj = prj;
}
@Override
public String[] getSupportedActions() {
return SUPPORTED_ACTIONS.toArray(new String[0]);
}
@NbBundle.Messages({
"# {0} - name of mx suite",
"MSG_Clean=mx clean {0}",
"# {0} - name of mx suite",
"MSG_Build=mx build {0}",
"# {0} - name of mx suite",
"# {1} - name of source group",
"MSG_BuildOnly=mx build {0} --only {1}",
"# {0} - name of mx suite",
"MSG_Rebuild=mx rebuild {0}",
"# {0} - name of mx suite",
"MSG_Unittest=mx unittest {0}",
})
@Override
@SuppressWarnings("fallthrough")
public void invokeAction(String action, Lookup context) throws IllegalArgumentException {
FileObject fo = context.lookup(FileObject.class);
String testSuffix = "";
CompletionStage<Integer> running;
switch (action) {
case ActionProvider.COMMAND_CLEAN:
running = runMx(Bundle.MSG_Clean(prj.getName()), "clean"); // NOI18N
break;
case ActionProvider.COMMAND_BUILD:
running = ensureBuilt(null);
break;
case ActionProvider.COMMAND_REBUILD:
running = ensureBuilt(null);
break;
case ActionProvider.COMMAND_COMPILE_SINGLE: {
running = ensureBuilt(fo);
break;
}
case SingleMethod.COMMAND_RUN_SINGLE_METHOD: {
SingleMethod m = context.lookup(SingleMethod.class);
if (m != null) {
if (fo == null) {
fo = m.getFile();
}
testSuffix = "#" + m.getMethodName();
}
// fallthrough
}
case ActionProvider.COMMAND_TEST_SINGLE:
case ActionProvider.COMMAND_RUN_SINGLE:
if (fo == null) {
Toolkit.getDefaultToolkit().beep();
return;
}
running = runBuildAndMx(null, Bundle.MSG_Unittest(fo.getName()), "unittest", fo.getName() + testSuffix); // NOI18N
break;
case SingleMethod.COMMAND_DEBUG_SINGLE_METHOD: {
SingleMethod m = context.lookup(SingleMethod.class);
if (m != null) {
if (fo == null) {
fo = m.getFile();
}
testSuffix = "#" + m.getMethodName();
}
// fallthrough
}
case ActionProvider.COMMAND_DEBUG_TEST_SINGLE:
case ActionProvider.COMMAND_DEBUG_SINGLE:
if (fo == null) {
Toolkit.getDefaultToolkit().beep();
return;
}
ListeningDICookie ldic = ListeningDICookie.create(-1);
DebuggerInfo di = DebuggerInfo.create(ListeningDICookie.ID, ldic);
ASYNC.post(() -> {
DebuggerManager.getDebuggerManager().startDebugging(di);
});
int port = ldic.getPortNumber();
running = runBuildAndMx(null, Bundle.MSG_Unittest(fo.getName()), "--attach", "" + port, "unittest", fo.getName() + testSuffix); // NOI18N
break;
default:
throw new UnsupportedOperationException(action);
}
if (running != null) {
ActionProgress progress = ActionProgress.start(context);
running.handle((exitCode, error) -> {
progress.finished(error == null && exitCode != null && exitCode == 0);
return null;
});
}
}
private CompletionStage<Integer> ensureBuilt(FileObject fo) {
if (fo != null) {
SuiteSources.Group grp = prj.getSources().findGroup(fo);
if (grp != null) {
final String name = grp.getDisplayName();
return runMx(Bundle.MSG_BuildOnly(prj.getName(), name), "build", "--only", name); // NOI18N
}
}
return runMx(Bundle.MSG_Rebuild(prj.getName()), "build"); // NOI18N
}
private CompletionStage<Integer> runBuildAndMx(FileObject fo, String taskName, String... args) {
return ensureBuilt(fo).thenCompose((exitCode) -> {
return runMx(taskName, args);
});
}
private CompletableFuture<Integer> runMx(String taskName, String... args) {
final File suiteDir = FileUtil.toFile(prj.getProjectDirectory());
if (!suiteDir.isDirectory()) {
Toolkit.getDefaultToolkit().beep();
return CompletableFuture.completedFuture(-1);
}
CompletableFuture<Future<Integer>> taskResult = new CompletableFuture<>();
CompletableFuture<Integer> cf = new CompletableFuture<>();
LifecycleManager.getDefault().saveAll();
ExecutionDescriptor descriptor = new ExecutionDescriptor()
.frontWindow(true).controllable(true)
.errConvertorFactory(() -> {
return (String line) -> {
String[] segments = line.split(":");
if (segments.length > 2) {
File src = new File(segments[0]);
if (src.exists()) {
int lineNumber = parseLineNumber(segments) - 1;
return Collections.singletonList(ConvertedLine.forText(line, new OutputListener() {
@Override
public void outputLineSelected(OutputEvent ev) {
openLine(Line.ShowOpenType.NONE, Line.ShowVisibilityType.FRONT);
}
@Override
public void outputLineAction(OutputEvent ev) {
openLine(Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS);
}
private boolean openLine(final Line.ShowOpenType openType, final Line.ShowVisibilityType visibilityType) throws IndexOutOfBoundsException {
FileObject fo = FileUtil.toFileObject(src);
if (fo != null) {
Lookup lkp = fo.getLookup();
final LineCookie lines = lkp.lookup(LineCookie.class);
if (lines != null) {
Line open = lines.getLineSet().getOriginal(lineNumber);
if (open != null) {
open.show(openType, visibilityType);
return true;
}
}
Openable open = lkp.lookup(Openable.class);
if (open != null) {
open.open();
} else {
Toolkit.getDefaultToolkit().beep();
}
}
return false;
}
@Override
public void outputLineCleared(OutputEvent ev) {
}
}));
}
}
return null;
};
}).postExecution((exitCode) -> {
cf.complete(exitCode);
});
ProcessBuilder processBuilder = ProcessBuilder.getLocal();
processBuilder.setWorkingDirectory(suiteDir.getPath());
String executable = TEST_MX_PATH != null ? TEST_MX_PATH : "mx"; // NOI18N
processBuilder.setExecutable(executable); // NOI18N
processBuilder.setArguments(Arrays.asList(args));
ExecutionService service = ExecutionService.newService(processBuilder, descriptor, taskName);
Future<Integer> task = service.run();
taskResult.complete(task);
prj.registerTask(task);
return cf;
}
private int parseLineNumber(String[] segments) {
int lineNumber;
try {
lineNumber = Integer.parseInt(segments[1]);
} catch (NumberFormatException ex) {
lineNumber = 1;
}
return lineNumber;
}
@Override
@SuppressWarnings("fallthrough")
public boolean isActionEnabled(String action, Lookup context) throws IllegalArgumentException {
FileObject fo = context.lookup(FileObject.class);
switch (action) {
case SingleMethod.COMMAND_DEBUG_SINGLE_METHOD:
case SingleMethod.COMMAND_RUN_SINGLE_METHOD:
SingleMethod m = context.lookup(SingleMethod.class);
if (fo == null && m != null) {
fo = m.getFile();
}
// fallthrough
case ActionProvider.COMMAND_COMPILE_SINGLE:
case ActionProvider.COMMAND_TEST_SINGLE:
case ActionProvider.COMMAND_RUN_SINGLE:
case ActionProvider.COMMAND_DEBUG_TEST_SINGLE:
case ActionProvider.COMMAND_DEBUG_SINGLE:
return fo != null;
default:
return SUPPORTED_ACTIONS.contains(action);
}
}
}