http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java
new file mode 100644
index 0000000..28f7225
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ForkDefinition.java
@@ -0,0 +1,142 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.optional.junitlauncher.StandaloneLauncher;
+import org.apache.tools.ant.types.Commandline;
+import org.apache.tools.ant.types.CommandlineJava;
+import org.apache.tools.ant.types.Environment;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PropertySet;
+
+/**
+ * Represents the {@code fork} element within test definitions of the
+ * {@code junitlauncher} task
+ */
+public class ForkDefinition {
+
+    private boolean includeAntRuntimeLibraries = true;
+    private boolean includeJUnitPlatformLibraries = true;
+
+    private final CommandlineJava commandLineJava;
+    private final Environment env = new Environment();
+
+    private String dir;
+    private long timeout = -1;
+
+    ForkDefinition() {
+        this.commandLineJava = new CommandlineJava();
+    }
+
+    public void setDir(final String dir) {
+        this.dir = dir;
+    }
+
+    String getDir() {
+        return this.dir;
+    }
+
+    public void setTimeout(final long timeout) {
+        this.timeout = timeout;
+    }
+
+    long getTimeout() {
+        return this.timeout;
+    }
+
+    public void setIncludeJUnitPlatformLibraries(final boolean include) {
+        this.includeJUnitPlatformLibraries = include;
+    }
+
+    public void setIncludeAntRuntimeLibraries(final boolean include) {
+        this.includeAntRuntimeLibraries = include;
+    }
+
+    public Commandline.Argument createJvmArg() {
+        return this.commandLineJava.createVmArgument();
+    }
+
+    public void addConfiguredSysProperty(final Environment.Variable sysProp) {
+        // validate that key/value are present
+        sysProp.validate();
+        this.commandLineJava.addSysproperty(sysProp);
+    }
+
+    public void addConfiguredSysPropertySet(final PropertySet propertySet) {
+        this.commandLineJava.addSyspropertyset(propertySet);
+    }
+
+    public void addConfiguredEnv(final Environment.Variable var) {
+        this.env.addVariable(var);
+    }
+
+    public void addConfiguredModulePath(final Path modulePath) {
+        
this.commandLineJava.createModulepath(modulePath.getProject()).add(modulePath);
+    }
+
+    public void addConfiguredUpgradeModulePath(final Path upgradeModulePath) {
+        
this.commandLineJava.createUpgrademodulepath(upgradeModulePath.getProject()).add(upgradeModulePath);
+    }
+
+    Environment getEnv() {
+        return this.env;
+    }
+
+    /**
+     * Generates a new {@link CommandlineJava} constructed out of the 
configurations set on this
+     * {@link ForkDefinition}
+     *
+     * @param task The junitlaunchertask for which this is a fork definition
+     * @return
+     */
+    CommandlineJava generateCommandLine(final JUnitLauncherTask task) {
+        final CommandlineJava cmdLine;
+        try {
+            cmdLine = (CommandlineJava) this.commandLineJava.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new BuildException(e);
+        }
+        cmdLine.setClassname(StandaloneLauncher.class.getName());
+        // VM arguments
+        final Project project = task.getProject();
+        final ClassLoader taskClassLoader = task.getClass().getClassLoader();
+        // Ant runtime classes
+        if (this.includeAntRuntimeLibraries) {
+            final Path antRuntimeResources = new Path(project);
+            
JUnitLauncherClassPathUtil.addAntRuntimeResourceLocations(antRuntimeResources, 
taskClassLoader);
+            final Path classPath = cmdLine.createClasspath(project);
+            classPath.createPath().append(antRuntimeResources);
+        } else {
+            task.log("Excluding Ant runtime libraries from forked JVM 
classpath", Project.MSG_DEBUG);
+        }
+        // JUnit platform classes
+        if (this.includeJUnitPlatformLibraries) {
+            final Path junitPlatformResources = new Path(project);
+            
JUnitLauncherClassPathUtil.addJUnitPlatformResourceLocations(junitPlatformResources,
 taskClassLoader);
+            final Path classPath = cmdLine.createClasspath(project);
+            classPath.createPath().append(junitPlatformResources);
+        } else {
+            task.log("Excluding JUnit platform libraries from forked JVM 
classpath", Project.MSG_DEBUG);
+        }
+        return cmdLine;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java
new file mode 100644
index 0000000..dcd4145
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherClassPathUtil.java
@@ -0,0 +1,76 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.launch.AntMain;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.util.LoaderUtils;
+
+import java.io.File;
+
+/**
+ *
+ */
+final class JUnitLauncherClassPathUtil {
+
+    private static final String RESOURCE_IN_PLATFORM_ENGINE = 
"org/junit/platform/engine/TestEngine.class";
+    private static final String RESOURCE_IN_PLATFORM_LAUNCHER = 
"org/junit/platform/launcher/core/LauncherFactory.class";
+    private static final String RESOURCE_IN_PLATFORM_COMMON = 
"org/junit/platform/commons/annotation/Testable.class";
+    private static final String RESOURCE_NAME_LAUNCHER_SUPPORT = 
"org/apache/tools/ant/taskdefs/optional/junitlauncher/LauncherSupport.class";
+
+
+    static void addAntRuntimeResourceLocations(final Path path, final 
ClassLoader classLoader) {
+        addResourceLocationToPath(path, classLoader, 
toResourceName(AntMain.class));
+        addResourceLocationToPath(path, classLoader, 
toResourceName(Task.class));
+        addResourceLocationToPath(path, classLoader, 
RESOURCE_NAME_LAUNCHER_SUPPORT);
+    }
+
+    static void addLauncherSupportResourceLocation(final Path path, final 
ClassLoader classLoader) {
+        addResourceLocationToPath(path, classLoader, 
RESOURCE_NAME_LAUNCHER_SUPPORT);
+    }
+
+    static void addJUnitPlatformResourceLocations(final Path path, final 
ClassLoader classLoader) {
+        // platform-engine
+        addResourceLocationToPath(path, classLoader, 
RESOURCE_IN_PLATFORM_ENGINE);
+        // platform-launcher
+        addResourceLocationToPath(path, classLoader, 
RESOURCE_IN_PLATFORM_LAUNCHER);
+        // platform-commons
+        addResourceLocationToPath(path, classLoader, 
RESOURCE_IN_PLATFORM_COMMON);
+    }
+
+    static boolean addResourceLocationToPath(final Path path, final 
ClassLoader classLoader, final String resource) {
+        final File f = LoaderUtils.getResourceSource(classLoader, resource);
+        if (f == null) {
+            return false;
+        }
+        path.createPath().setLocation(f);
+        return true;
+    }
+
+    static boolean hasJUnitPlatformResources(final ClassLoader cl) {
+        final File f = LoaderUtils.getResourceSource(cl, 
RESOURCE_IN_PLATFORM_ENGINE);
+        return f != null;
+    }
+
+    private static String toResourceName(final Class klass) {
+        final String name = klass.getName();
+        return name.replaceAll("\\.", "/") + ".class";
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java
new file mode 100644
index 0000000..654211e
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/JUnitLauncherTask.java
@@ -0,0 +1,436 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.AntClassLoader;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Execute;
+import org.apache.tools.ant.taskdefs.ExecuteWatchdog;
+import org.apache.tools.ant.taskdefs.LogOutputStream;
+import org.apache.tools.ant.taskdefs.PumpStreamHandler;
+import org.apache.tools.ant.types.CommandlineJava;
+import org.apache.tools.ant.types.Environment;
+import org.apache.tools.ant.types.Path;
+
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.TimeoutException;
+
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_HALT_ON_FAILURE;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_PRINT_SUMMARY;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LAUNCH_DEF;
+
+/**
+ * An Ant {@link Task} responsible for launching the JUnit platform for 
running tests.
+ * This requires a minimum of JUnit 5, since that's the version in which the 
JUnit platform launcher
+ * APIs were introduced.
+ * <p>
+ * This task in itself doesn't run the JUnit tests, instead the sole 
responsibility of
+ * this task is to setup the JUnit platform launcher, build requests, launch 
those requests and then parse the
+ * result of the execution to present in a way that's been configured on this 
Ant task.
+ * </p>
+ * <p>
+ * Furthermore, this task allows users control over which classes to select 
for passing on to the JUnit 5
+ * platform for test execution. It however, is solely the JUnit 5 platform, 
backed by test engines that
+ * decide and execute the tests.
+ *
+ * @see <a href="https://junit.org/junit5/";>JUnit 5 documentation</a>
+ */
+public class JUnitLauncherTask extends Task {
+
+    private static final String LAUNCHER_SUPPORT_CLASS_NAME = 
"org.apache.tools.ant.taskdefs.optional.junitlauncher.LauncherSupport";
+    private static final String IN_VM_TEST_EXECUTION_CONTEXT_CLASS_NAME = 
"org.apache.tools.ant.taskdefs.optional.junitlauncher.InVMExecution";
+    private static final String TEST_EXECUTION_CONTEXT_CLASS_NAME = 
"org.apache.tools.ant.taskdefs.optional.junitlauncher.TestExecutionContext";
+
+    private Path classPath;
+    private boolean haltOnFailure;
+    private String failureProperty;
+    private boolean printSummary;
+    private final List<TestDefinition> tests = new ArrayList<>();
+    private final List<ListenerDefinition> listeners = new ArrayList<>();
+
+    public JUnitLauncherTask() {
+    }
+
+    @Override
+    public void execute() throws BuildException {
+        if (this.tests.isEmpty()) {
+            return;
+        }
+        final Project project = getProject();
+        for (final TestDefinition test : this.tests) {
+            if (!test.shouldRun(project)) {
+                log("Excluding test " + test + " since it's considered not to 
run " +
+                        "in context of project " + project, Project.MSG_DEBUG);
+                continue;
+            }
+            if (test.getForkDefinition() != null) {
+                forkTest(test);
+            } else {
+                launchViaReflection(new 
InVMLaunch(Collections.singletonList(test)));
+            }
+        }
+    }
+
+    /**
+     * Adds the {@link Path} to the classpath which will be used for execution 
of the tests
+     *
+     * @param path The classpath
+     */
+    public void addConfiguredClassPath(final Path path) {
+        if (this.classPath == null) {
+            // create a "wrapper" path which can hold on to multiple
+            // paths that get passed to this method (if at all the task in the 
build is
+            // configured with multiple classpaht elements)
+            this.classPath = new Path(getProject());
+        }
+        this.classPath.add(path);
+    }
+
+    /**
+     * Adds a {@link SingleTestClass} that will be passed on to the underlying 
JUnit platform
+     * for possible execution of the test
+     *
+     * @param test The test
+     */
+    public void addConfiguredTest(final SingleTestClass test) {
+        this.preConfigure(test);
+        this.tests.add(test);
+    }
+
+    /**
+     * Adds {@link TestClasses} that will be passed on to the underlying JUnit 
platform for
+     * possible execution of the tests
+     *
+     * @param testClasses The test classes
+     */
+    public void addConfiguredTestClasses(final TestClasses testClasses) {
+        this.preConfigure(testClasses);
+        this.tests.add(testClasses);
+    }
+
+    /**
+     * Adds a {@link ListenerDefinition listener} which will be enrolled for 
listening to test
+     * execution events
+     *
+     * @param listener The listener
+     */
+    public void addConfiguredListener(final ListenerDefinition listener) {
+        this.listeners.add(listener);
+    }
+
+    public void setHaltonfailure(final boolean haltonfailure) {
+        this.haltOnFailure = haltonfailure;
+    }
+
+    public void setFailureProperty(final String failureProperty) {
+        this.failureProperty = failureProperty;
+    }
+
+    public void setPrintSummary(final boolean printSummary) {
+        this.printSummary = printSummary;
+    }
+
+    private void preConfigure(final TestDefinition test) {
+        if (test.getHaltOnFailure() == null) {
+            test.setHaltOnFailure(this.haltOnFailure);
+        }
+        if (test.getFailureProperty() == null) {
+            test.setFailureProperty(this.failureProperty);
+        }
+    }
+
+    private void launchViaReflection(final InVMLaunch launchDefinition) {
+        final ClassLoader cl = launchDefinition.getClassLoader();
+        // instantiate a new TestExecutionContext instance using the launch 
definition's classloader
+        final Class<?> testExecutionCtxClass;
+        final Object testExecutionCtx;
+        try {
+            testExecutionCtxClass = 
Class.forName(TEST_EXECUTION_CONTEXT_CLASS_NAME, false, cl);
+            final Class<?> klass = 
Class.forName(IN_VM_TEST_EXECUTION_CONTEXT_CLASS_NAME, false, cl);
+            testExecutionCtx = 
klass.getConstructor(JUnitLauncherTask.class).newInstance(this);
+        } catch (Exception e) {
+            throw new BuildException("Failed to create a test execution 
context for in-vm tests", e);
+        }
+        // instantiate a new LauncherSupport instance using the launch 
definition's ClassLoader
+        try {
+            final Class<?> klass = Class.forName(LAUNCHER_SUPPORT_CLASS_NAME, 
false, cl);
+            final Object launcherSupport = 
klass.getConstructor(LaunchDefinition.class, testExecutionCtxClass)
+                    .newInstance(launchDefinition, testExecutionCtx);
+            klass.getMethod("launch").invoke(launcherSupport);
+        } catch (Exception e) {
+            throw new BuildException("Failed to launch in-vm tests", e);
+        }
+    }
+
+    private java.nio.file.Path dumpProjectProperties() throws IOException {
+        final java.nio.file.Path propsPath = Files.createTempFile(null, 
"properties");
+        propsPath.toFile().deleteOnExit();
+        final Hashtable<String, Object> props = 
this.getProject().getProperties();
+        final Properties projProperties = new Properties();
+        projProperties.putAll(props);
+        try (final OutputStream os = Files.newOutputStream(propsPath)) {
+            // TODO: Is it always UTF-8?
+            projProperties.store(os, StandardCharsets.UTF_8.name());
+        }
+        return propsPath;
+    }
+
+    private void forkTest(final TestDefinition test) {
+        // create launch command
+        final ForkDefinition forkDefinition = test.getForkDefinition();
+        final CommandlineJava commandlineJava = 
forkDefinition.generateCommandLine(this);
+        if (this.classPath != null) {
+            
commandlineJava.createClasspath(getProject()).createPath().append(this.classPath);
+        }
+        final java.nio.file.Path projectPropsPath;
+        try {
+            projectPropsPath = dumpProjectProperties();
+        } catch (IOException e) {
+            throw new BuildException("Could not create the necessary 
properties file while forking a process" +
+                    " for a test", e);
+        }
+        // --properties <path-to-properties-file>
+        commandlineJava.createArgument().setValue(Constants.ARG_PROPERTIES);
+        
commandlineJava.createArgument().setValue(projectPropsPath.toAbsolutePath().toString());
+
+        final java.nio.file.Path launchDefXmlPath = newLaunchDefinitionXml();
+        try (final OutputStream os = Files.newOutputStream(launchDefXmlPath)) {
+            final XMLStreamWriter writer = 
XMLOutputFactory.newFactory().createXMLStreamWriter(os, "UTF-8");
+            try {
+                writer.writeStartDocument();
+                writer.writeStartElement(LD_XML_ELM_LAUNCH_DEF);
+                if (this.printSummary) {
+                    writer.writeAttribute(LD_XML_ATTR_PRINT_SUMMARY, "true");
+                }
+                if (this.haltOnFailure) {
+                    writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, "true");
+                }
+                // task level listeners
+                for (final ListenerDefinition listenerDef : this.listeners) {
+                    if (!listenerDef.shouldUse(getProject())) {
+                        continue;
+                    }
+                    // construct the listener definition argument
+                    listenerDef.toForkedRepresentation(writer);
+                }
+                // test definition as XML
+                test.toForkedRepresentation(this, writer);
+                writer.writeEndElement();
+                writer.writeEndDocument();
+            } finally {
+                writer.close();
+            }
+        } catch (Exception e) {
+            throw new BuildException("Failed to construct command line for 
test", e);
+        }
+        // --launch-definition <xml-file-path>
+        
commandlineJava.createArgument().setValue(Constants.ARG_LAUNCH_DEFINITION);
+        
commandlineJava.createArgument().setValue(launchDefXmlPath.toAbsolutePath().toString());
+
+        // launch the process and wait for process to complete
+        final int exitCode = executeForkedTest(forkDefinition, 
commandlineJava);
+        switch (exitCode) {
+            case Constants.FORK_EXIT_CODE_SUCCESS: {
+                // success
+                break;
+            }
+            case Constants.FORK_EXIT_CODE_EXCEPTION: {
+                // process failed with some exception
+                throw new BuildException("Forked test(s) failed with an 
exception");
+            }
+            case Constants.FORK_EXIT_CODE_TESTS_FAILED: {
+                // test has failure(s)
+                try {
+                    if (test.getFailureProperty() != null) {
+                        // if there are test failures and the test is 
configured to set a property in case
+                        // of failure, then set the property to true
+                        
this.getProject().setNewProperty(test.getFailureProperty(), "true");
+                    }
+                } finally {
+                    if (test.isHaltOnFailure()) {
+                        // if the test is configured to halt on test failures, 
throw a build error
+                        final String errorMessage;
+                        if (test instanceof NamedTest) {
+                            errorMessage = "Test " + ((NamedTest) 
test).getName() + " has failure(s)";
+                        } else {
+                            errorMessage = "Some test(s) have failure(s)";
+                        }
+                        throw new BuildException(errorMessage);
+                    }
+                }
+                break;
+            }
+            case Constants.FORK_EXIT_CODE_TIMED_OUT: {
+                throw new BuildException(new TimeoutException("Forked test(s) 
timed out"));
+            }
+        }
+    }
+
+    private int executeForkedTest(final ForkDefinition forkDefinition, final 
CommandlineJava commandlineJava) {
+        final LogOutputStream outStream = new LogOutputStream(this, 
Project.MSG_INFO);
+        final LogOutputStream errStream = new LogOutputStream(this, 
Project.MSG_WARN);
+        final ExecuteWatchdog watchdog = forkDefinition.getTimeout() > 0 ? new 
ExecuteWatchdog(forkDefinition.getTimeout()) : null;
+        final Execute execute = new Execute(new PumpStreamHandler(outStream, 
errStream), watchdog);
+        execute.setCommandline(commandlineJava.getCommandline());
+        execute.setAntRun(getProject());
+        if (forkDefinition.getDir() != null) {
+            
execute.setWorkingDirectory(Paths.get(forkDefinition.getDir()).toFile());
+        }
+        final Environment env = forkDefinition.getEnv();
+        if (env != null && env.getVariables() != null) {
+            execute.setEnvironment(env.getVariables());
+        }
+        log(commandlineJava.describeCommand(), Project.MSG_VERBOSE);
+        int exitCode;
+        try {
+            exitCode = execute.execute();
+        } catch (IOException e) {
+            throw new BuildException("Process fork failed", e, getLocation());
+        }
+        return (watchdog != null && watchdog.killedProcess()) ? 
Constants.FORK_EXIT_CODE_TIMED_OUT : exitCode;
+    }
+
+    private java.nio.file.Path newLaunchDefinitionXml() {
+        final java.nio.file.Path xmlFilePath;
+        try {
+            xmlFilePath = Files.createTempFile(null, ".xml");
+        } catch (IOException e) {
+            throw new BuildException("Failed to construct command line for 
test", e);
+        }
+        xmlFilePath.toFile().deleteOnExit();
+        return xmlFilePath;
+    }
+
+    private final class InVMLaunch implements LaunchDefinition {
+
+        private final List<TestDefinition> inVMTests;
+        private final ClassLoader executionCL;
+
+        private InVMLaunch(final List<TestDefinition> inVMTests) {
+            this.inVMTests = inVMTests;
+            this.executionCL = createInVMExecutionClassLoader();
+        }
+
+        @Override
+        public List<TestDefinition> getTests() {
+            return this.inVMTests;
+        }
+
+        @Override
+        public List<ListenerDefinition> getListeners() {
+            return listeners;
+        }
+
+        @Override
+        public boolean isPrintSummary() {
+            return printSummary;
+        }
+
+        @Override
+        public boolean isHaltOnFailure() {
+            return haltOnFailure;
+        }
+
+        @Override
+        public ClassLoader getClassLoader() {
+            return this.executionCL;
+        }
+
+        private ClassLoader createInVMExecutionClassLoader() {
+            final Path taskConfiguredClassPath = 
JUnitLauncherTask.this.classPath;
+            if (taskConfiguredClassPath == null) {
+                // no specific classpath configured for the task, so use the 
classloader
+                // of this task
+                return JUnitLauncherTask.class.getClassLoader();
+            }
+            // there's a classpath configured for the task.
+            // we first check if the Ant runtime classpath has JUnit platform 
classes.
+            // - if it does, then we use the Ant runtime classpath plus the 
task's configured classpath
+            // with the traditional parent first loading.
+            // - else (i.e. Ant runtime classpath doesn't have JUnit platform 
classes), then we
+            // expect/assume the task's configured classpath to have the JUnit 
platform classes and we
+            // then create a "overriding" classloader which prefers certain 
resources (specifically the classes
+            // from org.apache.tools.ant.taskdefs.optional.junitlauncher 
package), from the task's
+            // classpath, even if the Ant's runtime classpath has those 
resources.
+            if 
(JUnitLauncherClassPathUtil.hasJUnitPlatformResources(JUnitLauncherTask.class.getClassLoader()))
 {
+                return new 
AntClassLoader(JUnitLauncherTask.class.getClassLoader(), getProject(), 
taskConfiguredClassPath, true);
+            }
+            final Path cp = new Path(getProject());
+            cp.add(taskConfiguredClassPath);
+            // add the Ant runtime resources to this path
+            JUnitLauncherClassPathUtil.addLauncherSupportResourceLocation(cp, 
JUnitLauncherTask.class.getClassLoader());
+            return new 
TaskConfiguredPathClassLoader(JUnitLauncherTask.class.getClassLoader(), cp, 
getProject());
+        }
+    }
+
+    /**
+     * A {@link ClassLoader}, very similar to the {@link 
org.apache.tools.ant.util.SplitClassLoader},
+     * which uses the {@link #TaskConfiguredPathClassLoader(ClassLoader, Path, 
Project) configured Path}
+     * to load a class, if the class belongs to the {@code 
org.apache.tools.ant.taskdefs.optional.junitlauncher}
+     * package.
+     * <p>
+     * While looking for classes belonging to the {@code 
org.apache.tools.ant.taskdefs.optional.junitlauncher}
+     * package, this classloader completely ignores Ant runtime classpath, 
even if that classpath has
+     * those classes. This allows the users of this classloader to use a 
custom location and thus more control over
+     * where these classes reside, when running the {@code junitlauncher} task
+     */
+    private final class TaskConfiguredPathClassLoader extends AntClassLoader {
+
+        /**
+         * @param parent  ClassLoader
+         * @param path    Path
+         * @param project Project
+         */
+        private TaskConfiguredPathClassLoader(ClassLoader parent, Path path, 
Project project) {
+            super(parent, project, path, true);
+        }
+
+        // forceLoadClass is not convenient here since it would not
+        // properly deal with inner classes of these classes.
+        @Override
+        protected synchronized Class<?> loadClass(String classname, boolean 
resolve)
+                throws ClassNotFoundException {
+            Class<?> theClass = findLoadedClass(classname);
+            if (theClass != null) {
+                return theClass;
+            }
+            final String packageName = classname.substring(0, 
classname.lastIndexOf('.'));
+            if 
(packageName.equals("org.apache.tools.ant.taskdefs.optional.junitlauncher")) {
+                theClass = findClass(classname);
+                if (resolve) {
+                    resolveClass(theClass);
+                }
+                return theClass;
+            }
+            return super.loadClass(classname, resolve);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java
new file mode 100644
index 0000000..10e7f4d
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/LaunchDefinition.java
@@ -0,0 +1,58 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import java.util.List;
+
+/**
+ * Defines the necessary context for launching the JUnit platform for running
+ * tests.
+ */
+public interface LaunchDefinition {
+
+    /**
+     * @return Returns the {@link TestDefinition tests} that have to be 
launched
+     */
+    List<TestDefinition> getTests();
+
+    /**
+     * @return Returns the default {@link ListenerDefinition listeners} that 
will be used
+     * for the tests, if the {@link #getTests() tests} themselves don't 
specify any
+     */
+    List<ListenerDefinition> getListeners();
+
+    /**
+     * @return Returns true if a summary needs to be printed out after the 
execution of the
+     * tests. False otherwise.
+     */
+    boolean isPrintSummary();
+
+    /**
+     * @return Returns true if any remaining tests launch need to be stopped 
if any test execution
+     * failed. False otherwise.
+     */
+    boolean isHaltOnFailure();
+
+    /**
+     * @return Returns the {@link ClassLoader} that has to be used for 
launching and execution of the
+     * tests
+     */
+    ClassLoader getClassLoader();
+
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java
new file mode 100644
index 0000000..9eaf147
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/ListenerDefinition.java
@@ -0,0 +1,208 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.PropertyHelper;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_CLASS_NAME;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_LISTENER_RESULT_FILE;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_SEND_SYS_ERR;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_SEND_SYS_OUT;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_LISTENER;
+
+/**
+ * Represents the {@code &lt;listener&gt;} element within the {@code 
&lt;junitlauncher&gt;}
+ * task
+ */
+public class ListenerDefinition {
+
+
+    private static final String LEGACY_PLAIN = "legacy-plain";
+    private static final String LEGACY_BRIEF = "legacy-brief";
+    private static final String LEGACY_XML = "legacy-xml";
+
+    private String ifProperty;
+    private String unlessProperty;
+    private String className;
+    private String resultFile;
+    private boolean sendSysOut;
+    private boolean sendSysErr;
+    private String outputDir;
+
+    public ListenerDefinition() {
+
+    }
+
+    public void setClassName(final String className) {
+        this.className = className;
+    }
+
+    public String getClassName() {
+        return this.className;
+    }
+
+    String getIfProperty() {
+        return ifProperty;
+    }
+
+    public void setIf(final String ifProperty) {
+        this.ifProperty = ifProperty;
+    }
+
+    String getUnlessProperty() {
+        return unlessProperty;
+    }
+
+    public void setUnless(final String unlessProperty) {
+        this.unlessProperty = unlessProperty;
+    }
+
+    public void setType(final ListenerType type) {
+        switch (type.getValue()) {
+            case LEGACY_PLAIN: {
+                
this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyPlainResultFormatter");
+                break;
+            }
+            case LEGACY_BRIEF: {
+                
this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyBriefResultFormatter");
+                break;
+            }
+            case LEGACY_XML: {
+                
this.setClassName("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyXmlResultFormatter");
+                break;
+            }
+        }
+    }
+
+    public void setResultFile(final String filename) {
+        this.resultFile = filename;
+    }
+
+    public String requireResultFile(final TestDefinition test) {
+        if (this.resultFile != null) {
+            return this.resultFile;
+        }
+        final StringBuilder sb = new StringBuilder("TEST-");
+        if (test instanceof NamedTest) {
+            sb.append(((NamedTest) test).getName());
+        } else {
+            sb.append("unknown");
+        }
+        sb.append(".");
+        final String suffix;
+        if 
("org.apache.tools.ant.taskdefs.optional.junitlauncher.LegacyXmlResultFormatter".equals(this.className))
 {
+            suffix = "xml";
+        } else {
+            suffix = "txt";
+        }
+        sb.append(suffix);
+        return sb.toString();
+    }
+
+    public void setSendSysOut(final boolean sendSysOut) {
+        this.sendSysOut = sendSysOut;
+    }
+
+    public boolean shouldSendSysOut() {
+        return this.sendSysOut;
+    }
+
+    public void setSendSysErr(final boolean sendSysErr) {
+        this.sendSysErr = sendSysErr;
+    }
+
+    public boolean shouldSendSysErr() {
+        return this.sendSysErr;
+    }
+
+    /**
+     * Sets the output directory for this listener
+     *
+     * @param dir Path to the output directory
+     * @since Ant 1.10.6
+     */
+    public void setOutputDir(final String dir) {
+        this.outputDir = dir;
+    }
+
+    public String getOutputDir() {
+        return this.outputDir;
+    }
+
+    public boolean shouldUse(final Project project) {
+        final PropertyHelper propertyHelper = 
PropertyHelper.getPropertyHelper(project);
+        return propertyHelper.testIfCondition(this.ifProperty) && 
propertyHelper.testUnlessCondition(this.unlessProperty);
+    }
+
+    public static class ListenerType extends EnumeratedAttribute {
+
+        @Override
+        public String[] getValues() {
+            return new String[]{LEGACY_PLAIN, LEGACY_BRIEF, LEGACY_XML};
+        }
+    }
+
+    void toForkedRepresentation(final XMLStreamWriter writer) throws 
XMLStreamException {
+        writer.writeStartElement(LD_XML_ELM_LISTENER);
+        writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, this.className);
+        writer.writeAttribute(LD_XML_ATTR_SEND_SYS_ERR, 
Boolean.toString(this.sendSysErr));
+        writer.writeAttribute(LD_XML_ATTR_SEND_SYS_OUT, 
Boolean.toString(this.sendSysOut));
+        if (this.resultFile != null) {
+            writer.writeAttribute(LD_XML_ATTR_LISTENER_RESULT_FILE, 
this.resultFile);
+        }
+        writer.writeEndElement();
+    }
+
+    public static ListenerDefinition fromForkedRepresentation(final 
XMLStreamReader reader) throws XMLStreamException {
+        reader.require(XMLStreamConstants.START_ELEMENT, null, 
LD_XML_ELM_LISTENER);
+        final ListenerDefinition listenerDef = new ListenerDefinition();
+        final String className = requireAttributeValue(reader, 
LD_XML_ATTR_CLASS_NAME);
+        listenerDef.setClassName(className);
+        final String sendSysErr = reader.getAttributeValue(null, 
LD_XML_ATTR_SEND_SYS_ERR);
+        if (sendSysErr != null) {
+            listenerDef.setSendSysErr(Boolean.parseBoolean(sendSysErr));
+        }
+        final String sendSysOut = reader.getAttributeValue(null, 
LD_XML_ATTR_SEND_SYS_OUT);
+        if (sendSysOut != null) {
+            listenerDef.setSendSysOut(Boolean.parseBoolean(sendSysOut));
+        }
+        final String resultFile = reader.getAttributeValue(null, 
LD_XML_ATTR_LISTENER_RESULT_FILE);
+        if (resultFile != null) {
+            listenerDef.setResultFile(resultFile);
+        }
+        reader.nextTag();
+        reader.require(XMLStreamConstants.END_ELEMENT, null, 
LD_XML_ELM_LISTENER);
+        return listenerDef;
+    }
+
+    private static String requireAttributeValue(final XMLStreamReader reader, 
final String attrName) throws XMLStreamException {
+        final String val = reader.getAttributeValue(null, attrName);
+        if (val != null) {
+            return val;
+        }
+        throw new XMLStreamException("Attribute " + attrName + " is missing at 
" + reader.getLocation());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java
new file mode 100644
index 0000000..295acd9
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/NamedTest.java
@@ -0,0 +1,29 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+/**
+ * A test that has a name associated with it
+ */
+public interface NamedTest {
+
+    /**
+     * @return Returns the name of the test
+     */
+    String getName();
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java
new file mode 100644
index 0000000..7c0186f
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/SingleTestClass.java
@@ -0,0 +1,171 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_CLASS_NAME;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_EXCLUDE_ENGINES;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_HALT_ON_FAILURE;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_INCLUDE_ENGINES;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_METHODS;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_OUTPUT_DIRECTORY;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_TEST;
+
+/**
+ * Represents the single {@code test} (class) that's configured to be launched 
by the {@link JUnitLauncherTask}
+ */
+public class SingleTestClass extends TestDefinition implements NamedTest {
+
+    private String testClass;
+    private Set<String> testMethods;
+
+    public SingleTestClass() {
+
+    }
+
+    public void setName(final String test) {
+        if (test == null || test.trim().isEmpty()) {
+            throw new IllegalArgumentException("Test name cannot be null or 
empty string");
+        }
+        this.testClass = test;
+    }
+
+    public String getName() {
+        return this.testClass;
+    }
+
+    public void setMethods(final String methods) {
+        // parse the comma separated set of methods
+        if (methods == null || methods.trim().isEmpty()) {
+            this.testMethods = Collections.emptySet();
+            return;
+        }
+        final StringTokenizer tokenizer = new StringTokenizer(methods, ",");
+        if (!tokenizer.hasMoreTokens()) {
+            this.testMethods = Collections.emptySet();
+            return;
+        }
+        // maintain specified ordering
+        this.testMethods = new LinkedHashSet<>();
+        while (tokenizer.hasMoreTokens()) {
+            final String method = tokenizer.nextToken().trim();
+            if (method.isEmpty()) {
+                continue;
+            }
+            this.testMethods.add(method);
+        }
+    }
+
+    boolean hasMethodsSpecified() {
+        return this.testMethods != null && !this.testMethods.isEmpty();
+    }
+
+    public String[] getMethods() {
+        if (!hasMethodsSpecified()) {
+            return null;
+        }
+        return this.testMethods.toArray(new String[this.testMethods.size()]);
+    }
+
+    @Override
+    protected void toForkedRepresentation(final JUnitLauncherTask task, final 
XMLStreamWriter writer) throws XMLStreamException {
+        writer.writeStartElement(LD_XML_ELM_TEST);
+        writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, testClass);
+        if (testMethods != null) {
+            final StringBuilder sb = new StringBuilder();
+            for (final String method : testMethods) {
+                if (sb.length() != 0) {
+                    sb.append(",");
+                }
+                sb.append(method);
+            }
+            writer.writeAttribute(LD_XML_ATTR_METHODS, sb.toString());
+        }
+        if (haltOnFailure != null) {
+            writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, 
haltOnFailure.toString());
+        }
+        if (outputDir != null) {
+            writer.writeAttribute(LD_XML_ATTR_OUTPUT_DIRECTORY, outputDir);
+        }
+        if (includeEngines != null) {
+            writer.writeAttribute(LD_XML_ATTR_INCLUDE_ENGINES, includeEngines);
+        }
+        if (excludeEngines != null) {
+            writer.writeAttribute(LD_XML_ATTR_EXCLUDE_ENGINES, excludeEngines);
+        }
+        // listeners for this test
+        if (listeners != null) {
+            for (final ListenerDefinition listenerDef : getListeners()) {
+                if (!listenerDef.shouldUse(task.getProject())) {
+                    // not applicable
+                    continue;
+                }
+                listenerDef.toForkedRepresentation(writer);
+            }
+        }
+        writer.writeEndElement();
+    }
+
+    public static TestDefinition fromForkedRepresentation(final 
XMLStreamReader reader) throws XMLStreamException {
+        reader.require(XMLStreamConstants.START_ELEMENT, null, 
LD_XML_ELM_TEST);
+        final SingleTestClass testDefinition = new SingleTestClass();
+        final String testClassName = requireAttributeValue(reader, 
LD_XML_ATTR_CLASS_NAME);
+        testDefinition.setName(testClassName);
+        final String methodNames = reader.getAttributeValue(null, 
LD_XML_ATTR_METHODS);
+        if (methodNames != null) {
+            testDefinition.setMethods(methodNames);
+        }
+        final String halt = reader.getAttributeValue(null, 
LD_XML_ATTR_HALT_ON_FAILURE);
+        if (halt != null) {
+            testDefinition.setHaltOnFailure(Boolean.parseBoolean(halt));
+        }
+        final String outDir = reader.getAttributeValue(null, 
LD_XML_ATTR_OUTPUT_DIRECTORY);
+        if (outDir != null) {
+            testDefinition.setOutputDir(outDir);
+        }
+        final String includeEngs = reader.getAttributeValue(null, 
LD_XML_ATTR_INCLUDE_ENGINES);
+        if (includeEngs != null) {
+            testDefinition.setIncludeEngines(includeEngs);
+        }
+        final String excludeEngs = reader.getAttributeValue(null, 
LD_XML_ATTR_EXCLUDE_ENGINES);
+        if (excludeEngs != null) {
+            testDefinition.setExcludeEngines(excludeEngs);
+        }
+        while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
+            reader.require(XMLStreamConstants.START_ELEMENT, null, 
Constants.LD_XML_ELM_LISTENER);
+            
testDefinition.addConfiguredListener(ListenerDefinition.fromForkedRepresentation(reader));
+        }
+        return testDefinition;
+    }
+
+    private static String requireAttributeValue(final XMLStreamReader reader, 
final String attrName) throws XMLStreamException {
+        final String val = reader.getAttributeValue(null, attrName);
+        if (val != null) {
+            return val;
+        }
+        throw new XMLStreamException("Attribute " + attrName + " is missing at 
" + reader.getLocation());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java
new file mode 100644
index 0000000..f119a34
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestClasses.java
@@ -0,0 +1,152 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.Resources;
+import org.apache.tools.ant.types.resources.StringResource;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_CLASS_NAME;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_EXCLUDE_ENGINES;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_HALT_ON_FAILURE;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_INCLUDE_ENGINES;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ATTR_OUTPUT_DIRECTORY;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_TEST;
+import static 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.Constants.LD_XML_ELM_TEST_CLASSES;
+
+/**
+ * Represents a {@code testclasses} that's configured to be launched by the 
{@link JUnitLauncherTask}
+ */
+public class TestClasses extends TestDefinition {
+
+    private final Resources resources = new Resources();
+
+    public TestClasses() {
+
+    }
+
+    public void add(final ResourceCollection resourceCollection) {
+        this.resources.add(resourceCollection);
+    }
+
+    public List<String> getTestClassNames() {
+        if (this.resources.isEmpty()) {
+            return Collections.emptyList();
+        }
+        final List<String> tests = new ArrayList<>();
+        for (final Resource resource : resources) {
+            if (!resource.isExists()) {
+                continue;
+            }
+            final String name = resource.getName();
+            // we only consider .class files
+            if (!name.endsWith(".class")) {
+                continue;
+            }
+            final String className = name.substring(0, name.lastIndexOf('.'));
+            tests.add(className.replace(File.separatorChar, '.').replace('/', 
'.').replace('\\', '.'));
+        }
+        return tests;
+    }
+
+    @Override
+    protected void toForkedRepresentation(final JUnitLauncherTask task, final 
XMLStreamWriter writer) throws XMLStreamException {
+        writer.writeStartElement(LD_XML_ELM_TEST_CLASSES);
+        // write out each test class
+        for (final String test : getTestClassNames()) {
+            writer.writeStartElement(LD_XML_ELM_TEST);
+            writer.writeAttribute(LD_XML_ATTR_CLASS_NAME, test);
+            if (haltOnFailure != null) {
+                writer.writeAttribute(LD_XML_ATTR_HALT_ON_FAILURE, 
haltOnFailure.toString());
+            }
+            if (outputDir != null) {
+                writer.writeAttribute(LD_XML_ATTR_OUTPUT_DIRECTORY, outputDir);
+            }
+            if (includeEngines != null) {
+                writer.writeAttribute(LD_XML_ATTR_INCLUDE_ENGINES, 
includeEngines);
+            }
+            if (excludeEngines != null) {
+                writer.writeAttribute(LD_XML_ATTR_EXCLUDE_ENGINES, 
excludeEngines);
+            }
+            // listeners for this test
+            if (listeners != null) {
+                for (final ListenerDefinition listenerDef : getListeners()) {
+                    if (!listenerDef.shouldUse(task.getProject())) {
+                        // not applicable
+                        continue;
+                    }
+                    listenerDef.toForkedRepresentation(writer);
+                }
+            }
+            writer.writeEndElement();
+        }
+        writer.writeEndElement();
+    }
+
+    public static List<TestDefinition> fromForkedRepresentation(final 
XMLStreamReader reader) throws XMLStreamException {
+        reader.require(XMLStreamConstants.START_ELEMENT, null, 
LD_XML_ELM_TEST_CLASSES);
+        final TestClasses testDefinition = new TestClasses();
+        // read out as multiple SingleTestClass representations
+        while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
+            reader.require(XMLStreamConstants.START_ELEMENT, null, 
Constants.LD_XML_ELM_TEST);
+            final String testClassName = requireAttributeValue(reader, 
LD_XML_ATTR_CLASS_NAME);
+            testDefinition.add(new StringResource(testClassName + ".class"));
+            final String halt = reader.getAttributeValue(null, 
LD_XML_ATTR_HALT_ON_FAILURE);
+            if (halt != null) {
+                testDefinition.setHaltOnFailure(Boolean.parseBoolean(halt));
+            }
+            final String outDir = reader.getAttributeValue(null, 
LD_XML_ATTR_OUTPUT_DIRECTORY);
+            if (outDir != null) {
+                testDefinition.setOutputDir(outDir);
+            }
+            final String includeEngs = reader.getAttributeValue(null, 
LD_XML_ATTR_INCLUDE_ENGINES);
+            if (includeEngs != null) {
+                testDefinition.setIncludeEngines(includeEngs);
+            }
+            final String excludeEngs = reader.getAttributeValue(null, 
LD_XML_ATTR_EXCLUDE_ENGINES);
+            if (excludeEngs != null) {
+                testDefinition.setExcludeEngines(excludeEngs);
+            }
+            while (reader.nextTag() != XMLStreamConstants.END_ELEMENT) {
+                reader.require(XMLStreamConstants.START_ELEMENT, null, 
Constants.LD_XML_ELM_LISTENER);
+                
testDefinition.addConfiguredListener(ListenerDefinition.fromForkedRepresentation(reader));
+            }
+            reader.require(XMLStreamConstants.END_ELEMENT, null, 
Constants.LD_XML_ELM_TEST);
+        }
+        reader.require(XMLStreamConstants.END_ELEMENT, null, 
LD_XML_ELM_TEST_CLASSES);
+        return Collections.singletonList(testDefinition);
+    }
+
+    private static String requireAttributeValue(final XMLStreamReader reader, 
final String attrName) throws XMLStreamException {
+        final String val = reader.getAttributeValue(null, attrName);
+        if (val != null) {
+            return val;
+        }
+        throw new XMLStreamException("Attribute " + attrName + " is missing at 
" + reader.getLocation());
+    }
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java
new file mode 100644
index 0000000..e2403e5
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/TestDefinition.java
@@ -0,0 +1,148 @@
+/*
+ *  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.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.PropertyHelper;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents the configuration details of a test that needs to be launched by 
the {@link JUnitLauncherTask}
+ */
+public abstract class TestDefinition {
+
+    protected String ifProperty;
+    protected String unlessProperty;
+    protected Boolean haltOnFailure;
+    protected String failureProperty;
+    protected String outputDir;
+    protected String includeEngines;
+    protected String excludeEngines;
+    protected ForkDefinition forkDefinition;
+
+    protected List<ListenerDefinition> listeners = new ArrayList<>();
+
+    String getIfProperty() {
+        return ifProperty;
+    }
+
+    public void setIf(final String ifProperty) {
+        this.ifProperty = ifProperty;
+    }
+
+    String getUnlessProperty() {
+        return unlessProperty;
+    }
+
+    public void setUnless(final String unlessProperty) {
+        this.unlessProperty = unlessProperty;
+    }
+
+    public boolean isHaltOnFailure() {
+        return this.haltOnFailure != null && this.haltOnFailure;
+    }
+
+    Boolean getHaltOnFailure() {
+        return this.haltOnFailure;
+    }
+
+    public void setHaltOnFailure(final boolean haltonfailure) {
+        this.haltOnFailure = haltonfailure;
+    }
+
+    public String getFailureProperty() {
+        return failureProperty;
+    }
+
+    public void setFailureProperty(final String failureProperty) {
+        this.failureProperty = failureProperty;
+    }
+
+    public void addConfiguredListener(final ListenerDefinition listener) {
+        this.listeners.add(listener);
+    }
+
+    public List<ListenerDefinition> getListeners() {
+        return Collections.unmodifiableList(this.listeners);
+    }
+
+    public void setOutputDir(final String dir) {
+        this.outputDir = dir;
+    }
+
+    public String getOutputDir() {
+        return this.outputDir;
+    }
+
+    public ForkDefinition createFork() {
+        if (this.forkDefinition != null) {
+            throw new BuildException("Test definition cannot have more than 
one fork elements");
+        }
+        this.forkDefinition = new ForkDefinition();
+        return this.forkDefinition;
+    }
+
+    ForkDefinition getForkDefinition() {
+        return this.forkDefinition;
+    }
+
+    protected boolean shouldRun(final Project project) {
+        final PropertyHelper propertyHelper = 
PropertyHelper.getPropertyHelper(project);
+        return propertyHelper.testIfCondition(this.ifProperty) && 
propertyHelper.testUnlessCondition(this.unlessProperty);
+    }
+
+    public String[] getIncludeEngines() {
+        return includeEngines == null ? new String[0] : 
split(this.includeEngines, ",");
+    }
+
+    public void setIncludeEngines(final String includeEngines) {
+        this.includeEngines = includeEngines;
+    }
+
+    public String[] getExcludeEngines() {
+        return excludeEngines == null ? new String[0] : 
split(this.excludeEngines, ",");
+    }
+
+    public void setExcludeEngines(final String excludeEngines) {
+        this.excludeEngines = excludeEngines;
+    }
+
+    private static String[] split(final String value, final String delimiter) {
+        if (value == null) {
+            return new String[0];
+        }
+        final List<String> parts = new ArrayList<>();
+        for (final String part : value.split(delimiter)) {
+            if (part.trim().isEmpty()) {
+                // skip it
+                continue;
+            }
+            parts.add(part);
+        }
+        return parts.toArray(new String[parts.size()]);
+    }
+
+    protected abstract void toForkedRepresentation(JUnitLauncherTask task, 
XMLStreamWriter writer) throws XMLStreamException;
+
+}

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java
new file mode 100644
index 0000000..773c7b1
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/confined/package-info.java
@@ -0,0 +1,27 @@
+/*
+ *  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.
+ *
+ */
+/**
+ * The classes/interfaces in this package <em>must not</em>
+ * have any compile time dependency on any of JUnit platform or
+ * engine classes/interfaces. They <em>must not</em> even have any
+ * compile time dependency on any classes/interfaces that belong to the
+ * {@link org.apache.tools.ant.taskdefs.optional.junitlauncher} package.
+ *
+ * @since Ant 1.10.6
+ */
+package org.apache.tools.ant.taskdefs.optional.junitlauncher.confined;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java
----------------------------------------------------------------------
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java
new file mode 100644
index 0000000..df75b9b
--- /dev/null
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/package-info.java
@@ -0,0 +1,28 @@
+/*
+ *  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.
+ *
+ */
+/**
+ * The classes/interfaces in this package are allowed
+ * to have compile time dependency on JUnit platform classes/interfaces.
+ * Furthermore classes/interfaces in this package are also
+ * allowed to have compile time dependency on the
+ * {@link org.apache.tools.ant.taskdefs.optional.junitlauncher.confined}
+ * package
+ *
+ * @since Ant 1.10.6
+ */
+package org.apache.tools.ant.taskdefs.optional.junitlauncher;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ant/blob/0cb9d22b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java
----------------------------------------------------------------------
diff --git 
a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java
 
b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java
index 97a40c1..024a87b 100644
--- 
a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java
+++ 
b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/JUnitLauncherTaskTest.java
@@ -20,6 +20,7 @@ package org.apache.tools.ant.taskdefs.optional.junitlauncher;
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.BuildFileRule;
 import org.apache.tools.ant.Project;
+import 
org.apache.tools.ant.taskdefs.optional.junitlauncher.confined.JUnitLauncherTask;
 import org.apache.tools.ant.util.LoaderUtils;
 import org.example.junitlauncher.jupiter.JupiterSampleTest;
 import org.example.junitlauncher.vintage.AlwaysFailingJUnit4Test;

Reply via email to