changeset 3344:31c8b18fdc5b jdk-9+115

8149757: Implement Multi-Release JAR aware JavacFileManager for javac Reviewed-by: jjg, jlahoda Contributed-by: steve.drach@oracle.com
author jjg
date Thu, 14 Apr 2016 17:51:30 -0700
parents eaa3ac6a778a
children dd5907bca0a4 bcf9765e73b1
files src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTool.java src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties test/tools/javac/file/MultiReleaseJar/MultiReleaseJarAwareSJFM.java test/tools/javac/file/MultiReleaseJar/MultiReleaseJarTest.java
diffstat 9 files changed, 495 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTool.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTool.java	Thu Apr 14 17:51:30 2016 -0700
@@ -46,10 +46,12 @@
 import com.sun.tools.javac.main.Option;
 import com.sun.tools.javac.file.BaseFileManager;
 import com.sun.tools.javac.file.CacheFSInfo;
+import com.sun.tools.javac.jvm.Target;
 import com.sun.tools.javac.util.ClientCodeException;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.DefinedBy;
 import com.sun.tools.javac.util.DefinedBy.Api;
+import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.Log;
 import com.sun.tools.javac.util.PropagatedException;
 
@@ -175,6 +177,14 @@
 
             Arguments args = Arguments.instance(context);
             args.init("javac", options, classes, compilationUnits);
+
+            // init multi-release jar handling
+            if (fileManager.isSupportedOption(Option.MULTIRELEASE.text) == 1) {
+                Target target = Target.instance(context);
+                List<String> list = List.of(target.multiReleaseValue());
+                fileManager.handleOption(Option.MULTIRELEASE.text, list.iterator());
+            }
+
             return new JavacTaskImpl(context);
         } catch (PropagatedException ex) {
             throw ex.getCause();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/BaseFileManager.java	Thu Apr 14 17:51:30 2016 -0700
@@ -286,6 +286,8 @@
         return -1;
     }
 
+    protected String multiReleaseValue;
+
     /**
      * Common back end for OptionHelper handleFileManagerOption.
      * @param option the option whose value to be set
@@ -298,6 +300,10 @@
                 encodingName = value;
                 return true;
 
+            case MULTIRELEASE:
+                multiReleaseValue = value;
+                return true;
+
             default:
                 return locations.handleOption(option, value);
         }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JavacFileManager.java	Thu Apr 14 17:51:30 2016 -0700
@@ -45,6 +45,7 @@
 import java.nio.file.ProviderNotFoundException;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.spi.FileSystemProvider;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -494,7 +495,12 @@
 
         public ArchiveContainer(Path archivePath) throws IOException, ProviderNotFoundException, SecurityException {
             this.archivePath = archivePath;
-            this.fileSystem = FileSystems.newFileSystem(archivePath, null);
+            if (multiReleaseValue != null && archivePath.toString().endsWith(".jar")) {
+                Map<String,String> env = Collections.singletonMap("multi-release", multiReleaseValue);
+                this.fileSystem = getJarFSProvider().newFileSystem(archivePath, env);
+            } else {
+                this.fileSystem = FileSystems.newFileSystem(archivePath, null);
+            }
         }
 
         /**
@@ -578,8 +584,23 @@
             fileSystem.close();
         }
     }
+
+    private FileSystemProvider jarFSProvider;
+
+    private FileSystemProvider getJarFSProvider() throws IOException {
+        if (jarFSProvider != null) {
+            return jarFSProvider;
+        }
+        for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
+            if (provider.getScheme().equals("jar")) {
+                return (jarFSProvider = provider);
+            }
+        }
+        throw new ProviderNotFoundException("no provider found for .jar files");
+    }
+
     /**
-     * container is a directory, a zip file, or a non-existant path.
+     * container is a directory, a zip file, or a non-existent path.
      */
     private boolean isValidFile(String s, Set<JavaFileObject.Kind> fileKinds) {
         JavaFileObject.Kind kind = getKind(s);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java	Thu Apr 14 17:51:30 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2016, 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
@@ -141,4 +141,10 @@
         return compareTo(JDK1_9) >= 0;
     }
 
+    /** Value of platform release used to access multi-release jar files
+     */
+    public String multiReleaseValue() {
+        return Integer.toString(this.ordinal() - Target.JDK1_1.ordinal() + 1);
+    }
+
 }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java	Thu Apr 14 17:51:30 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2016, 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
@@ -42,6 +42,7 @@
 import com.sun.tools.javac.file.CacheFSInfo;
 import com.sun.tools.javac.file.BaseFileManager;
 import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.jvm.Target;
 import com.sun.tools.javac.platform.PlatformDescription;
 import com.sun.tools.javac.processing.AnnotationProcessingError;
 import com.sun.tools.javac.util.*;
@@ -230,7 +231,7 @@
         if (args.isEmpty())
             return Result.OK;
 
-        // init Depeendencies
+        // init Dependencies
         if (options.isSet("completionDeps")) {
             Dependencies.GraphDependencies.preRegister(context);
         }
@@ -242,6 +243,13 @@
             t.initPlugins(pluginOpts);
         }
 
+        // init multi-release jar handling
+        if (fileManager.isSupportedOption(Option.MULTIRELEASE.text) == 1) {
+            Target target = Target.instance(context);
+            List<String> list = List.of(target.multiReleaseValue());
+            fileManager.handleOption(Option.MULTIRELEASE.text, list.iterator());
+        }
+
         // init JavaCompiler
         JavaCompiler comp = JavaCompiler.instance(context);
 
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java	Thu Apr 14 17:51:30 2016 -0700
@@ -667,7 +667,9 @@
             }
             return false;
         }
-    };
+    },
+
+    MULTIRELEASE("-multi-release", "opt.arg.multi-release", "opt.multi-release", HIDDEN, FILEMANAGER);
 
     /** The kind of an Option. This is used by the -help and -X options. */
     public enum OptionKind {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties	Thu Apr 14 14:30:48 2016 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties	Thu Apr 14 17:51:30 2016 -0700
@@ -99,6 +99,8 @@
     Specify whether or not to generate class files for implicitly referenced files
 javac.opt.pkginfo=\
     Specify handling of package-info files
+javac.opt.multi-release=\
+    Specify which release to use in multi-release jars
 javac.opt.arg.class=\
     <class>
 javac.opt.arg.class.list=\
@@ -133,6 +135,8 @@
     Name and optional arguments for a plug-in to be run
 javac.opt.arg.plugin=\
     "name args"
+javac.opt.arg.multi-release=\
+    <release>
 
 ## extended options
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/file/MultiReleaseJar/MultiReleaseJarAwareSJFM.java	Thu Apr 14 17:51:30 2016 -0700
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+/*
+ * @test
+ * @bug 8149757
+ * @summary Test that StandardJavaFileManager uses the correct version of a
+ * class from a multi-release jar on classpath
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.ToolBox
+ * @run testng MultiReleaseJarAwareSJFM
+ */
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.List;
+
+import toolbox.JarTask;
+import toolbox.JavacTask;
+import toolbox.ToolBox;
+
+public class MultiReleaseJarAwareSJFM {
+
+    private final String version8 =
+            "package version;\n" +
+            "\n" +
+            "public class Version {\n" +
+            "    public int getVersion() {\n" +
+            "        return 8;\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String version9 =
+            "package version;\n" +
+            "\n" +
+            "public class Version {\n" +
+            "    public int getVersion() {\n" +
+            "        int version = (new PackagePrivate()).getVersion();\n" +
+            "        if (version == 9) return 9;\n" +
+            "        return version;\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String packagePrivate =
+            "package version;\n" +
+            "\n" +
+            "class PackagePrivate {\n" +
+            "    int getVersion() {\n" +
+            "        return 9;\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String version10 =
+            "package version;\n" +
+            "\n" +
+            "public class Version {\n" +
+            "    public int getVersion() {\n" +
+            "        return 10;\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String manifest =
+            "Manifest-Version: 1.0\n" +
+            "Multi-Release: true\n";
+
+    private final ToolBox tb = new ToolBox();
+
+    private final JavaFileManager.Location jloc = new JavaFileManager.Location() {
+        @Override
+        public String getName() {
+            return "Multi-Release Jar";
+        }
+        @Override
+        public boolean isOutputLocation() {
+            return false;
+        }
+    };
+
+    @BeforeClass
+    public void setup() throws Exception {
+        tb.createDirectories("classes",
+                "classes/META-INF/versions/9",
+                "classes/META-INF/versions/10");
+        new JavacTask(tb)
+                .outdir("classes")
+                .sources(version8)
+                .run();
+        new JavacTask(tb)
+                .outdir("classes/META-INF/versions/9")
+                .sources(version9, packagePrivate)
+                .run();
+        new JavacTask(tb)
+                .outdir("classes/META-INF/versions/10")
+                .sources(version10)
+                .run();
+        new JarTask(tb, "multi-release.jar")
+                .manifest(manifest)
+                .baseDir("classes")
+                .files("version/Version.class",
+                        "META-INF/versions/9/version/Version.class",
+                        "META-INF/versions/9/version/PackagePrivate.class",
+                        "META-INF/versions/10/version/Version.class")
+                .run();
+    }
+
+    @AfterClass
+    public void teardown() throws Exception {
+        tb.deleteFiles(
+                "classes/META-INF/versions/10/version/Version.class",
+                "classes/META-INF/versions/10/version",
+                "classes/META-INF/versions/10/",
+                "classes/META-INF/versions/9/version/Version.class",
+                "classes/META-INF/versions/9/version/PackagePrivate.class",
+                "classes/META-INF/versions/9/version",
+                "classes/META-INF/versions/9",
+                "classes/META-INF/versions",
+                "classes/META-INF",
+                "classes/version/Version.class",
+                "classes/version",
+                "classes",
+                "multi-release.jar"
+        );
+    }
+
+    @DataProvider(name = "versions")
+    public Object[][] data() {
+        return new Object[][] {
+                {"", 8},
+                {"8", 8},
+                {"9", 9},
+                {"runtime", jdk.Version.current().major()}
+        };
+    }
+
+    @Test(dataProvider = "versions")
+    public void test(String version, int expected) throws Throwable {
+        StandardJavaFileManager jfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
+        jfm.setLocation(jloc, List.of(new File("multi-release.jar")));
+
+        if (version.length() > 0) {
+            jfm.handleOption("-multi-release", List.of(version).iterator());
+        }
+
+        CustomClassLoader cldr = new CustomClassLoader(jfm);
+        Class<?> versionClass = cldr.loadClass("version.Version");
+        MethodType mt = MethodType.methodType(int.class);
+        MethodHandle mh = MethodHandles.lookup().findVirtual(versionClass, "getVersion", mt);
+        int v = (int)mh.invoke(versionClass.newInstance());
+        Assert.assertEquals(v, expected);
+
+        jfm.close();
+    }
+
+    private class CustomClassLoader extends ClassLoader {
+        private final JavaFileManager jfm;
+
+        public CustomClassLoader(JavaFileManager jfm) {
+            super(null);
+            this.jfm = jfm;
+        }
+
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            int n = name.lastIndexOf('.');
+            String pkg = n == -1 ? "" : name.substring(0, n);
+            String cls = name.substring(n + 1) + ".class";
+            byte[] b;
+            try {
+                FileObject obj = jfm.getFileForInput(jloc, pkg, cls);
+                try (InputStream is = obj.openInputStream()) {
+                    b = is.readAllBytes();
+                }
+            } catch (IOException x) {
+                throw new ClassNotFoundException(x.getMessage(), x);
+            }
+            return defineClass(name, b, 0, b.length);
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/tools/javac/file/MultiReleaseJar/MultiReleaseJarTest.java	Thu Apr 14 17:51:30 2016 -0700
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+/*
+ * @test
+ * @bug 8149757
+ * @summary Test that javac uses the correct version of a class from a
+ *          multi-release jar on classpath
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
+ * @run testng MultiReleaseJarTest
+ */
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import toolbox.JarTask;
+import toolbox.JavacTask;
+import toolbox.Task;
+import toolbox.ToolBox;
+
+
+public class MultiReleaseJarTest {
+
+    private final String main1 =
+            "class Main {\n" +
+            "    Info info = new Info();\n" +
+            "        \n" +
+            "    void run() {\n" +
+            "       System.out.println(info.get());\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String main2 =
+            "class Main {\n" +
+            "    Info info = new Info();\n" +
+            "        \n" +
+            "    void run() {\n" +
+            "       System.out.println(info.getInfo());\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String info1 =
+            "class Info {\n" +
+            "    String get() {\n" +
+            "       return \"some info\";\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String info2 =
+            "class Info {\n" +
+            "    String getInfo() {\n" +
+            "       return \"some info\";\n" +
+            "    }\n" +
+            "}\n";
+
+    private final String manifest =
+        "Manifest-Version: 1.0\n" +
+        "Multi-Release: true\n";
+
+    private final ToolBox tb = new ToolBox();
+
+    @BeforeClass
+    public void setup() throws Exception {
+        tb.createDirectories("classes", "classes/META-INF/versions/9");
+        new JavacTask(tb)
+                .outdir("classes")
+                .sources(info1)
+                .run();
+        new JavacTask(tb)
+                .outdir("classes/META-INF/versions/9")
+                .sources(info2)
+                .run();
+        // This is a bogus multi-release jar file since the two Info classes
+        // do not have the same public interface
+        new JarTask(tb, "multi-release.jar")
+                .manifest(manifest)
+                .baseDir("classes")
+                .files("Info.class", "META-INF/versions/9/Info.class")
+                .run();
+        tb.deleteFiles(
+                "classes/META-INF/versions/9/Info.class",
+                "classes/META-INF/versions/9",
+                "classes/META-INF/versions",
+                "classes/META-INF",
+                "classes/Info.class"
+        );
+    }
+
+    @AfterClass
+    public void teardown() throws Exception {
+        tb.deleteFiles(
+                "multi-release.jar",
+                "classes/Main.class",
+                "classes"
+        );
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -cp multi-release.jar Main.java -> fails
+    public void main1Runtime(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main1);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run(Task.Expect.FAIL, 1);
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -release 8 -cp multi-release.jar Main.java -> succeeds
+    public void main1Release8(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main1);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .options("-release", "8")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run();
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -release 9 -cp multi-release.jar Main.java -> fails
+    public void main1Release9(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main1);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .options("-release", "9")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run(Task.Expect.FAIL, 1);
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -cp multi-release.jar Main.java -> succeeds
+    public void main2Runtime(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main2);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run();
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -release 8 -cp multi-release.jar Main.java -> fails
+    public void main2Release8(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main2);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .options("-release", "8")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run(Task.Expect.FAIL, 1);
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+    }
+
+    @Test(dataProvider="modes")
+    // javac -d classes -release 9 -cp multi-release.jar Main.java -> succeeds
+    public void main2Release9(Task.Mode mode) throws Exception {
+        tb.writeFile("Main.java", main2);
+        Task.Result result = new JavacTask(tb, mode)
+                .outdir("classes")
+                .options("-release", "9")
+                .classpath("multi-release.jar")
+                .files("Main.java")
+                .run();
+        result.writeAll();
+        tb.deleteFiles("Main.java");
+    }
+
+    @DataProvider(name="modes")
+    public Object[][] createModes() {
+        return new Object[][] {
+            new Object[] {Task.Mode.API},
+            new Object[] {Task.Mode.CMDLINE},
+            new Object[] {Task.Mode.EXEC},
+        };
+    }
+}
+