diff a/test/lib/jdk/test/lib/combo/ComboTask.java b/test/lib/jdk/test/lib/combo/ComboTask.java
--- /dev/null
+++ b/test/lib/jdk/test/lib/combo/ComboTask.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.test.lib.combo;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.util.Assert;
+import com.sun.tools.javac.util.List;
+import jdk.test.lib.combo.ComboParameter.Resolver;
+
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * This class represents a compilation task associated with a combo test instance. This is a small
+ * wrapper around {@link JavacTask} which allows for fluent setup style and which makes use of
+ * the shared compilation context to speedup performances.
+ */
+public class ComboTask {
+
+    /** Sources to be compiled in this task. */
+    private List<JavaFileObject> sources = List.nil();
+
+    /** Options associated with this task. */
+    private List<String> options = List.nil();
+
+    /** Diagnostic collector. */
+    private DiagnosticCollector diagsCollector = new DiagnosticCollector();
+
+    /** Output writer. */
+    private Writer out;
+
+    /** Listeners associated with this task. */
+    private List<TaskListener> listeners = List.nil();
+
+    /** Underlying javac task object. */
+    private JavacTask task;
+
+    /** Combo execution environment. */
+    private ComboTestHelper<?>.Env env;
+
+    ComboTask(ComboTestHelper<?>.Env env) {
+        this.env = env;
+    }
+
+    /**
+     * Add a new source to this task.
+     */
+    public ComboTask withSource(JavaFileObject comboSource) {
+        sources = sources.prepend(comboSource);
+        return this;
+    }
+
+    /**
+     * Add a new template source with given name to this task; the template is replaced with
+     * corresponding combo parameters (as defined in the combo test environment).
+     */
+    public ComboTask withSourceFromTemplate(String name, String template) {
+        return withSource(new ComboTemplateSource(name, template));
+    }
+
+    /**
+     * Add a new template source with default name ("Test") to this task; the template is replaced with
+     * corresponding combo parameters (as defined in the combo test environment).
+     */
+    public ComboTask withSourceFromTemplate(String template) {
+        return withSource(new ComboTemplateSource("Test", template));
+    }
+
+    /**
+     * Add a new template source with given name to this task; the template is replaced with
+     * corresponding combo parameters (as defined in the combo test environment). A custom resolver
+     * is used to add combo parameter mappings to the current combo test environment.
+     */
+    public ComboTask withSourceFromTemplate(String name, String template, Resolver resolver) {
+        return withSource(new ComboTemplateSource(name, template, resolver));
+    }
+
+    /**
+     * Add a new template source with default name ("Test") to this task; the template is replaced with
+     * corresponding combo parameters (as defined in the combo test environment). A custom resolver
+     * is used to add combo parameter mappings to the current combo test environment.
+     */
+    public ComboTask withSourceFromTemplate(String template, Resolver resolver) {
+        return withSource(new ComboTemplateSource("Test", template, resolver));
+    }
+
+    /**
+     * Add a new option to this task.
+     */
+    public ComboTask withOption(String opt) {
+        options = options.append(opt);
+        return this;
+    }
+
+    /**
+     * Add a set of options to this task.
+     */
+    public ComboTask withOptions(String[] opts) {
+        for (String opt : opts) {
+            options = options.append(opt);
+        }
+        return this;
+    }
+
+    /**
+     * Add a set of options to this task.
+     */
+    public ComboTask withOptions(Iterable<? extends String> opts) {
+        for (String opt : opts) {
+            options = options.append(opt);
+        }
+        return this;
+    }
+
+    /**
+     * Set the output writer associated with this task.
+     */
+    public ComboTask withWriter(Writer out) {
+        this.out = out;
+        return this;
+    }
+
+    /**
+     * Add a task listener to this task.
+     */
+    public ComboTask withListener(TaskListener listener) {
+        listeners = listeners.prepend(listener);
+        return this;
+    }
+
+    /**
+     * Parse the sources associated with this task.
+     */
+    public Result<Iterable<? extends CompilationUnitTree>> parse() throws IOException {
+        return new Result<>(getTask().parse());
+    }
+
+    /**
+     * Parse and analyzes the sources associated with this task.
+     */
+    public Result<Iterable<? extends Element>> analyze() throws IOException {
+        return new Result<>(getTask().analyze());
+    }
+
+    /**
+     * Parse, analyze and perform code generation for the sources associated with this task.
+     */
+    public Result<Iterable<? extends JavaFileObject>> generate() throws IOException {
+        return new Result<>(getTask().generate());
+    }
+
+    /**
+     * Parse, analyze, perform code generation for the sources associated with this task and finally
+     * executes them
+     */
+    public <Z> Optional<Z> execute(Function<ExecutionTask, Z> executionFunc) throws IOException {
+        Result<Iterable<? extends JavaFileObject>> generationResult = generate();
+        Iterable<? extends JavaFileObject> jfoIterable = generationResult.get();
+        if (generationResult.hasErrors()) {
+            // we have nothing else to do
+            return Optional.empty();
+        }
+        java.util.List<URL> urlList = new ArrayList<>();
+        for (JavaFileObject jfo : jfoIterable) {
+            String urlStr = jfo.toUri().toURL().toString();
+            urlStr = urlStr.substring(0, urlStr.length() - jfo.getName().length());
+            urlList.add(new URL(urlStr));
+        }
+        return Optional.of(
+                executionFunc.apply(
+                        new ExecutionTask(new URLClassLoader(urlList.toArray(new URL[urlList.size()])))));
+    }
+
+    /**
+     * Fork a new compilation task; if possible the compilation context from previous executions is
+     * retained (see comments in ReusableContext as to when it's safe to do so); otherwise a brand
+     * new context is created.
+     */
+    public JavacTask getTask() {
+        if (task == null) {
+            ReusableContext context = env.context();
+            String opts = options == null ? "" :
+                    StreamSupport.stream(options.spliterator(), false).collect(Collectors.joining());
+            context.clear();
+            if (!context.polluted && (context.opts == null || context.opts.equals(opts))) {
+                //we can reuse former context
+                env.info().ctxReusedCount++;
+            } else {
+                env.info().ctxDroppedCount++;
+                //it's not safe to reuse context - create a new one
+                context = env.setContext(new ReusableContext());
+            }
+            context.opts = opts;
+            JavacTask javacTask = ((JavacTool)env.javaCompiler()).getTask(out, env.fileManager(),
+                    diagsCollector, options, null, sources, context);
+            javacTask.setTaskListener(context);
+            for (TaskListener l : listeners) {
+                javacTask.addTaskListener(l);
+            }
+            task = javacTask;
+        }
+        return task;
+    }
+
+    /**
+     * This class represents an execution task. It allows the execution of one or more classes previously
+     * added to a given class loader. This class uses reflection to execute any given static public method
+     * in any given class. It's not restricted to the execution of the {@code main} method
+     */
+    public class ExecutionTask {
+        private ClassLoader classLoader;
+        private String methodName = "main";
+        private Class<?>[] parameterTypes = new Class<?>[]{String[].class};
+        private Object[] args = new String[0];
+        private Consumer<Throwable> handler;
+        private Class<?> c;
+
+        private ExecutionTask(ClassLoader classLoader) {
+            this.classLoader = classLoader;
+        }
+
+        /**
+         * Set the name of the class to be loaded.
+         */
+        public ExecutionTask withClass(String className) {
+            Assert.check(className != null, "class name value is null, impossible to proceed");
+            try {
+                c = classLoader.loadClass(className);
+            } catch (Throwable t) {
+                throw new IllegalStateException(t);
+            }
+            return this;
+        }
+
+        /**
+         * Set the name of the method to be executed along with the parameter types to
+         * reflectively obtain the method.
+         */
+        public ExecutionTask withMethod(String methodName, Class<?>... parameterTypes) {
+            this.methodName = methodName;
+            this.parameterTypes = parameterTypes;
+            return this;
+        }
+
+        /**
+         * Set the arguments to be passed to the method.
+         */
+        public ExecutionTask withArguments(Object... args) {
+            this.args = args;
+            return this;
+        }
+
+        /**
+         * Set a handler to handle any exception thrown.
+         */
+        public ExecutionTask withHandler(Consumer<Throwable> handler) {
+            this.handler = handler;
+            return this;
+        }
+
+        /**
+         * Executes the given method in the given class. Returns true if the execution was
+         * successful, false otherwise.
+         */
+        public Object run() {
+            try {
+                java.lang.reflect.Method meth = c.getMethod(methodName, parameterTypes);
+                meth.invoke(null, (Object)args);
+                return true;
+            } catch (Throwable t) {
+                if (handler != null) {
+                    handler.accept(t);
+                }
+                return false;
+            }
+        }
+    }
+
+    /**
+     * This class is used to help clients accessing the results of a given compilation task.
+     * Contains several helper methods to inspect diagnostics generated during the task execution.
+     */
+    public class Result<D> {
+
+        /** The underlying compilation results. */
+        private final D data;
+
+        public Result(D data) {
+            this.data = data;
+        }
+
+        public D get() {
+            return data;
+        }
+
+        /**
+         * Did this task generate any error diagnostics?
+         */
+        public boolean hasErrors() {
+            return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.ERROR);
+        }
+
+        /**
+         * Did this task generate any warning diagnostics?
+         */
+        public boolean hasWarnings() {
+            return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.WARNING);
+        }
+
+        /**
+         * Did this task generate any note diagnostics?
+         */
+        public boolean hasNotes() {
+            return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.NOTE);
+        }
+
+        /**
+         * Did this task generate any diagnostic with given key?
+         */
+        public boolean containsKey(String key) {
+            return diagsCollector.diagsByKeys.containsKey(key);
+        }
+
+        /**
+         * Retrieve the list of diagnostics of a given kind.
+         */
+        public List<Diagnostic<? extends JavaFileObject>> diagnosticsForKind(Diagnostic.Kind kind) {
+            List<Diagnostic<? extends JavaFileObject>> diags = diagsCollector.diagsByKind.get(kind);
+            return diags != null ? diags : List.nil();
+        }
+
+        /**
+         * Retrieve the list of diagnostics with given key.
+         */
+        public List<Diagnostic<? extends JavaFileObject>> diagnosticsForKey(String key) {
+            List<Diagnostic<? extends JavaFileObject>> diags = diagsCollector.diagsByKeys.get(key);
+            return diags != null ? diags : List.nil();
+        }
+
+        /**
+         * Dump useful info associated with this task.
+         */
+        public String compilationInfo() {
+            return "instance#" + env.info().comboCount + ":[ options = " + options
+                    + ", diagnostics = " + diagsCollector.diagsByKeys.keySet()
+                    + ", dimensions = " + env.bindings
+                    + ", sources = \n" + sources.stream().map(s -> {
+                try {
+                    return s.getCharContent(true);
+                } catch (IOException ex) {
+                    return "";
+                }
+            }).collect(Collectors.joining(",")) + "]";
+        }
+    }
+
+    /**
+     * This class represents a Java source file whose contents are defined in terms of a template
+     * string. The holes in such template are expanded using corresponding combo parameter
+     * instances which can be retrieved using a resolver object.
+     */
+    class ComboTemplateSource extends SimpleJavaFileObject {
+
+        String source;
+        Map<String, ComboParameter> localParametersCache = new HashMap<>();
+
+        protected ComboTemplateSource(String name, String template) {
+            this(name, template, null);
+        }
+
+        protected ComboTemplateSource(String name, String template, Resolver resolver) {
+            super(URI.create("myfo:/" + env.info().comboCount + "/" + name + ".java"), Kind.SOURCE);
+            source = ComboParameter.expandTemplate(template, pname -> resolveParameter(pname, resolver));
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return source;
+        }
+
+        /**
+         * Combo parameter resolver function. First parameters are looked up in the global environment,
+         * then the local environment is looked up as a fallback.
+         */
+        ComboParameter resolveParameter(String pname, Resolver resolver) {
+            //first search the env
+            ComboParameter parameter = env.parametersCache.get(pname);
+            if (parameter == null) {
+                //then lookup local cache
+                parameter = localParametersCache.get(pname);
+                if (parameter == null && resolver != null) {
+                    //if still null and we have a custom resolution function, try that
+                    parameter = resolver.lookup(pname);
+                    if (parameter != null) {
+                       //if a match was found, store it in the local cache to aviod redundant recomputation
+                       localParametersCache.put(pname, parameter);
+                    }
+                }
+            }
+            return parameter;
+        }
+    }
+
+    /**
+     * Helper class to collect all diagnostic generated during the execution of a given compilation task.
+     */
+    class DiagnosticCollector implements DiagnosticListener<JavaFileObject> {
+
+        Map<Diagnostic.Kind, List<Diagnostic<? extends JavaFileObject>>> diagsByKind = new HashMap<>();
+        Map<String, List<Diagnostic<? extends JavaFileObject>>> diagsByKeys = new HashMap<>();
+
+        public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+            List<Diagnostic<? extends JavaFileObject>> diags =
+                    diagsByKeys.getOrDefault(diagnostic.getCode(), List.nil());
+            diagsByKeys.put(diagnostic.getCode(), diags.prepend(diagnostic));
+            Diagnostic.Kind kind = diagnostic.getKind();
+            diags = diagsByKind.getOrDefault(kind, List.nil());
+            diagsByKind.put(kind, diags.prepend(diagnostic));
+        }
+    }
+}
