1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.lang.reflect.Method;
  26 import java.nio.file.Files;
  27 import java.nio.file.Path;
  28 import java.nio.file.Paths;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.List;
  32 import java.util.function.Consumer;
  33 import java.util.jar.JarEntry;
  34 import java.util.jar.JarInputStream;
  35 import java.util.jar.JarOutputStream;
  36 import java.util.stream.Stream;
  37 
  38 import jdk.test.lib.util.FileUtils;
  39 import jdk.testlibrary.JDKToolFinder;
  40 import org.testng.annotations.BeforeTest;
  41 import org.testng.annotations.Test;
  42 
  43 import static java.lang.String.format;
  44 import static java.lang.System.out;
  45 import static java.nio.charset.StandardCharsets.UTF_8;
  46 import static org.testng.Assert.assertFalse;
  47 import static org.testng.Assert.assertTrue;
  48 
  49 /*
  50  * @test
  51  * @bug 8170952
  52  * @library /lib/testlibrary /test/lib
  53  * @build jdk.testlibrary.JDKToolFinder
  54  * @run testng CLICompatibility
  55  * @summary Basic test for compatibility of CLI options
  56  */
  57 
  58 public class CLICompatibility {
  59     static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
  60     static final Path USER_DIR = Paths.get(System.getProperty("user.dir"));
  61 
  62     static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", "");
  63 
  64     final boolean legacyOnly;  // for running on older JDK's ( test validation )
  65 
  66     // Resources we know to exist, that can be used for creating jar files.
  67     static final String RES1 = "CLICompatibility.class";
  68     static final String RES2 = "CLICompatibility$Result.class";
  69 
  70     @BeforeTest
  71     public void setupResourcesForJar() throws Exception {
  72         // Copy the files that we are going to use for creating/updating test
  73         // jar files, so that they can be referred to without '-C dir'
  74         Files.copy(TEST_CLASSES.resolve(RES1), USER_DIR.resolve(RES1));
  75         Files.copy(TEST_CLASSES.resolve(RES2), USER_DIR.resolve(RES2));
  76     }
  77 
  78     static final IOConsumer<InputStream> ASSERT_CONTAINS_RES1 = in -> {
  79         try (JarInputStream jin = new JarInputStream(in)) {
  80             assertTrue(jarContains(jin, RES1), "Failed to find " + RES1);
  81         }
  82     };
  83     static final IOConsumer<InputStream> ASSERT_CONTAINS_RES2 = in -> {
  84         try (JarInputStream jin = new JarInputStream(in)) {
  85             assertTrue(jarContains(jin, RES2), "Failed to find " + RES2);
  86         }
  87     };
  88     static final IOConsumer<InputStream> ASSERT_CONTAINS_MAINFEST = in -> {
  89         try (JarInputStream jin = new JarInputStream(in)) {
  90             assertTrue(jin.getManifest() != null, "No META-INF/MANIFEST.MF");
  91         }
  92     };
  93     static final IOConsumer<InputStream> ASSERT_DOES_NOT_CONTAIN_MAINFEST = in -> {
  94         try (JarInputStream jin = new JarInputStream(in)) {
  95             assertTrue(jin.getManifest() == null, "Found unexpected META-INF/MANIFEST.MF");
  96         }
  97     };
  98 
  99     static final FailCheckerWithMessage FAIL_TOO_MANY_MAIN_OPS =
 100         new FailCheckerWithMessage("You may not specify more than one '-cuxtid' options",
 101         /* legacy */ "{ctxui}[vfmn0Me] [jar-file] [manifest-file] [entry-point] [-C dir] files");
 102 
 103     // Create
 104 
 105     @Test
 106     public void createBadArgs() {
 107         final FailCheckerWithMessage FAIL_CREATE_NO_ARGS = new FailCheckerWithMessage(
 108                 "'c' flag requires manifest or input files to be specified!");
 109 
 110         jar("c")
 111             .assertFailure()
 112             .resultChecker(FAIL_CREATE_NO_ARGS);
 113 
 114         jar("-c")
 115             .assertFailure()
 116             .resultChecker(FAIL_CREATE_NO_ARGS);
 117 
 118         if (!legacyOnly)
 119             jar("--create")
 120                 .assertFailure()
 121                 .resultChecker(FAIL_CREATE_NO_ARGS);
 122 
 123         jar("ct")
 124             .assertFailure()
 125             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 126 
 127         jar("-ct")
 128             .assertFailure()
 129             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 130 
 131         if (!legacyOnly)
 132             jar("--create --list")
 133                 .assertFailure()
 134                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 135     }
 136 
 137     @Test
 138     public void createWriteToFile() throws IOException {
 139         Path path = Paths.get("createJarFile.jar");  // for creating
 140         String jn = path.toString();
 141         for (String opts : new String[]{"cf " + jn, "-cf " + jn, "--create --file=" + jn}) {
 142             if (legacyOnly && opts.startsWith("--"))
 143                 continue;
 144 
 145             jar(opts, RES1)
 146                 .assertSuccess()
 147                 .resultChecker(r -> {
 148                     ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
 149                     ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
 150                 });
 151         }
 152         FileUtils.deleteFileIfExistsWithRetry(path);
 153     }
 154 
 155     @Test
 156     public void createWriteToStdout() throws IOException {
 157         for (String opts : new String[]{"c", "-c", "--create"}) {
 158             if (legacyOnly && opts.startsWith("--"))
 159                 continue;
 160 
 161             jar(opts, RES1)
 162                 .assertSuccess()
 163                 .resultChecker(r -> {
 164                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
 165                     ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
 166                 });
 167         }
 168     }
 169 
 170     @Test
 171     public void createWriteToStdoutNoManifest() throws IOException {
 172         for (String opts : new String[]{"cM", "-cM", "--create --no-manifest"} ){
 173             if (legacyOnly && opts.startsWith("--"))
 174                 continue;
 175 
 176             jar(opts, RES1)
 177                 .assertSuccess()
 178                 .resultChecker(r -> {
 179                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
 180                     ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
 181                 });
 182         }
 183     }
 184 
 185     // Update
 186 
 187     @Test
 188     public void updateBadArgs() {
 189         final FailCheckerWithMessage FAIL_UPDATE_NO_ARGS = new FailCheckerWithMessage(
 190                 "'u' flag requires manifest, 'e' flag or input files to be specified!");
 191 
 192         jar("u")
 193             .assertFailure()
 194             .resultChecker(FAIL_UPDATE_NO_ARGS);
 195 
 196         jar("-u")
 197             .assertFailure()
 198             .resultChecker(FAIL_UPDATE_NO_ARGS);
 199 
 200         if (!legacyOnly)
 201             jar("--update")
 202                 .assertFailure()
 203                 .resultChecker(FAIL_UPDATE_NO_ARGS);
 204 
 205         jar("ut")
 206             .assertFailure()
 207             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 208 
 209         jar("-ut")
 210             .assertFailure()
 211             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 212 
 213         if (!legacyOnly)
 214             jar("--update --list")
 215                 .assertFailure()
 216                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 217     }
 218 
 219     @Test
 220     public void updateReadFileWriteFile() throws IOException {
 221         Path path = Paths.get("updateReadWriteStdout.jar");  // for updating
 222         String jn = path.toString();
 223 
 224         for (String opts : new String[]{"uf " + jn, "-uf " + jn, "--update --file=" + jn}) {
 225             if (legacyOnly && opts.startsWith("--"))
 226                 continue;
 227 
 228             createJar(path, RES1);
 229             jar(opts, RES2)
 230                 .assertSuccess()
 231                 .resultChecker(r -> {
 232                     ASSERT_CONTAINS_RES1.accept(Files.newInputStream(path));
 233                     ASSERT_CONTAINS_RES2.accept(Files.newInputStream(path));
 234                     ASSERT_CONTAINS_MAINFEST.accept(Files.newInputStream(path));
 235                 });
 236         }
 237         FileUtils.deleteFileIfExistsWithRetry(path);
 238     }
 239 
 240     @Test
 241     public void updateReadStdinWriteStdout() throws IOException {
 242         Path path = Paths.get("updateReadStdinWriteStdout.jar");
 243 
 244         for (String opts : new String[]{"u", "-u", "--update"}) {
 245             if (legacyOnly && opts.startsWith("--"))
 246                 continue;
 247 
 248             createJar(path, RES1);
 249             jarWithStdin(path.toFile(), opts, RES2)
 250                 .assertSuccess()
 251                 .resultChecker(r -> {
 252                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
 253                     ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
 254                     ASSERT_CONTAINS_MAINFEST.accept(r.stdoutAsStream());
 255                 });
 256         }
 257         FileUtils.deleteFileIfExistsWithRetry(path);
 258     }
 259 
 260     @Test
 261     public void updateReadStdinWriteStdoutNoManifest() throws IOException {
 262         Path path = Paths.get("updateReadStdinWriteStdoutNoManifest.jar");
 263 
 264         for (String opts : new String[]{"uM", "-uM", "--update --no-manifest"} ){
 265             if (legacyOnly && opts.startsWith("--"))
 266                 continue;
 267 
 268             createJar(path, RES1);
 269             jarWithStdin(path.toFile(), opts, RES2)
 270                 .assertSuccess()
 271                 .resultChecker(r -> {
 272                     ASSERT_CONTAINS_RES1.accept(r.stdoutAsStream());
 273                     ASSERT_CONTAINS_RES2.accept(r.stdoutAsStream());
 274                     ASSERT_DOES_NOT_CONTAIN_MAINFEST.accept(r.stdoutAsStream());
 275                 });
 276         }
 277         FileUtils.deleteFileIfExistsWithRetry(path);
 278     }
 279 
 280     // List
 281 
 282     @Test
 283     public void listBadArgs() {
 284         jar("tx")
 285             .assertFailure()
 286             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 287 
 288         jar("-tx")
 289             .assertFailure()
 290             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 291 
 292         if (!legacyOnly)
 293             jar("--list --extract")
 294                 .assertFailure()
 295                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 296     }
 297 
 298     @Test
 299     public void listReadFromFileWriteToStdout() throws IOException {
 300         Path path = Paths.get("listReadFromFileWriteToStdout.jar");  // for listing
 301         createJar(path, RES1);
 302         String jn = path.toString();
 303 
 304         for (String opts : new String[]{"tf " + jn, "-tf " + jn, "--list --file " + jn}) {
 305             if (legacyOnly && opts.startsWith("--"))
 306                 continue;
 307 
 308             jar(opts)
 309                 .assertSuccess()
 310                 .resultChecker(r ->
 311                     assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
 312                                "Failed, got [" + r.output + "]")
 313                 );
 314         }
 315         FileUtils.deleteFileIfExistsWithRetry(path);
 316     }
 317 
 318     @Test
 319     public void listReadFromStdinWriteToStdout() throws IOException {
 320         Path path = Paths.get("listReadFromStdinWriteToStdout.jar");
 321         createJar(path, RES1);
 322 
 323         for (String opts : new String[]{"t", "-t", "--list"} ){
 324             if (legacyOnly && opts.startsWith("--"))
 325                 continue;
 326 
 327             jarWithStdin(path.toFile(), opts)
 328                 .assertSuccess()
 329                 .resultChecker(r ->
 330                     assertTrue(r.output.contains("META-INF/MANIFEST.MF") && r.output.contains(RES1),
 331                                "Failed, got [" + r.output + "]")
 332                 );
 333         }
 334         FileUtils.deleteFileIfExistsWithRetry(path);
 335     }
 336 
 337     // Extract
 338 
 339     @Test
 340     public void extractBadArgs() {
 341         jar("xi")
 342             .assertFailure()
 343             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 344 
 345         jar("-xi")
 346             .assertFailure()
 347             .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 348 
 349         if (!legacyOnly) {
 350             jar("--extract --generate-index")
 351                 .assertFailure()
 352                 .resultChecker(new FailCheckerWithMessage(
 353                                    "option --generate-index requires an argument"));
 354 
 355             jar("--extract --generate-index=foo")
 356                 .assertFailure()
 357                 .resultChecker(FAIL_TOO_MANY_MAIN_OPS);
 358         }
 359     }
 360 
 361     @Test
 362     public void extractReadFromStdin() throws IOException {
 363         Path path = Paths.get("extract");
 364         Path jarPath = path.resolve("extractReadFromStdin.jar"); // for extracting
 365         createJar(jarPath, RES1);
 366 
 367         for (String opts : new String[]{"x" ,"-x", "--extract"}) {
 368             if (legacyOnly && opts.startsWith("--"))
 369                 continue;
 370 
 371             jarWithStdinAndWorkingDir(jarPath.toFile(), path.toFile(), opts)
 372                 .assertSuccess()
 373                 .resultChecker(r ->
 374                     assertTrue(Files.exists(path.resolve(RES1)),
 375                                "Expected to find:" + path.resolve(RES1))
 376                 );
 377             FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
 378         }
 379         FileUtils.deleteFileTreeWithRetry(path);
 380     }
 381 
 382     @Test
 383     public void extractReadFromFile() throws IOException {
 384         Path path = Paths.get("extract");
 385         String jn = "extractReadFromFile.jar";
 386         Path jarPath = path.resolve(jn);
 387         createJar(jarPath, RES1);
 388 
 389         for (String opts : new String[]{"xf "+jn ,"-xf "+jn, "--extract --file "+jn}) {
 390             if (legacyOnly && opts.startsWith("--"))
 391                 continue;
 392 
 393             jarWithStdinAndWorkingDir(null, path.toFile(), opts)
 394                 .assertSuccess()
 395                 .resultChecker(r ->
 396                     assertTrue(Files.exists(path.resolve(RES1)),
 397                                "Expected to find:" + path.resolve(RES1))
 398                 );
 399             FileUtils.deleteFileIfExistsWithRetry(path.resolve(RES1));
 400         }
 401         FileUtils.deleteFileTreeWithRetry(path);
 402     }
 403 
 404     // Basic help
 405 
 406     @Test
 407     public void helpBadOptionalArg() {
 408         if (legacyOnly)
 409             return;
 410 
 411         jar("--help:")
 412             .assertFailure();
 413 
 414         jar("--help:blah")
 415             .assertFailure();
 416     }
 417 
 418     @Test
 419     public void help() {
 420         if (legacyOnly)
 421             return;
 422 
 423         jar("-h")
 424             .assertSuccess()
 425             .resultChecker(r ->
 426                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
 427                            "Failed, got [" + r.output + "]")
 428             );
 429 
 430         jar("--help")
 431             .assertSuccess()
 432             .resultChecker(r -> {
 433                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
 434                            "Failed, got [" + r.output + "]");
 435                 assertFalse(r.output.contains("--do-not-resolve-by-default"));
 436                 assertFalse(r.output.contains("--warn-if-resolved"));
 437             });
 438 
 439         jar("--help:compat")
 440             .assertSuccess()
 441             .resultChecker(r ->
 442                 assertTrue(r.output.startsWith("Compatibility Interface:"),
 443                            "Failed, got [" + r.output + "]")
 444             );
 445 
 446         jar("--help-extra")
 447             .assertSuccess()
 448             .resultChecker(r -> {
 449                 assertTrue(r.output.startsWith("Usage: jar [OPTION...] [ [--release VERSION] [-C dir] files]"),
 450                            "Failed, got [" + r.output + "]");
 451                 assertTrue(r.output.contains("--do-not-resolve-by-default"));
 452                 assertTrue(r.output.contains("--warn-if-resolved"));
 453             });
 454     }
 455 
 456     // -- Infrastructure
 457 
 458     static boolean jarContains(JarInputStream jis, String entryName)
 459         throws IOException
 460     {
 461         JarEntry e;
 462         boolean found = false;
 463         while((e = jis.getNextJarEntry()) != null) {
 464             if (e.getName().equals(entryName))
 465                 return true;
 466         }
 467         return false;
 468     }
 469 
 470     /* Creates a simple jar with entries of size 0, good enough for testing */
 471     static void createJar(Path path, String... entries) throws IOException {
 472         FileUtils.deleteFileIfExistsWithRetry(path);
 473         Path parent = path.getParent();
 474         if (parent != null)
 475             Files.createDirectories(parent);
 476         try (OutputStream out = Files.newOutputStream(path);
 477              JarOutputStream jos = new JarOutputStream(out)) {
 478             JarEntry je = new JarEntry("META-INF/MANIFEST.MF");
 479             jos.putNextEntry(je);
 480             jos.closeEntry();
 481 
 482             for (String entry : entries) {
 483                 je = new JarEntry(entry);
 484                 jos.putNextEntry(je);
 485                 jos.closeEntry();
 486             }
 487         }
 488     }
 489 
 490     static class FailCheckerWithMessage implements Consumer<Result> {
 491         final String[] messages;
 492         FailCheckerWithMessage(String... m) {
 493             messages = m;
 494         }
 495         @Override
 496         public void accept(Result r) {
 497             //out.printf("%s%n", r.output);
 498             boolean found = false;
 499             for (String m : messages) {
 500                 if (r.output.contains(m)) {
 501                     found = true;
 502                     break;
 503                 }
 504             }
 505             assertTrue(found,
 506                        "Excepted out to contain one of: " + Arrays.asList(messages)
 507                            + " but got: " + r.output);
 508         }
 509     }
 510 
 511     static Result jar(String... args) {
 512         return jarWithStdinAndWorkingDir(null, null, args);
 513     }
 514 
 515     static Result jarWithStdin(File stdinSource, String... args) {
 516         return jarWithStdinAndWorkingDir(stdinSource, null, args);
 517     }
 518 
 519     static Result jarWithStdinAndWorkingDir(File stdinFrom,
 520                                             File workingDir,
 521                                             String... args) {
 522         String jar = getJDKTool("jar");
 523         List<String> commands = new ArrayList<>();
 524         commands.add(jar);
 525         if (!TOOL_VM_OPTIONS.isEmpty()) {
 526             commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
 527         }
 528         Stream.of(args).map(s -> s.split(" "))
 529                        .flatMap(Arrays::stream)
 530                        .forEach(x -> commands.add(x));
 531         ProcessBuilder p = new ProcessBuilder(commands);
 532         if (stdinFrom != null)
 533             p.redirectInput(stdinFrom);
 534         if (workingDir != null)
 535             p.directory(workingDir);
 536         return run(p);
 537     }
 538 
 539     static Result run(ProcessBuilder pb) {
 540         Process p;
 541         byte[] stdout, stderr;
 542         out.printf("Running: %s%n", pb.command());
 543         try {
 544             p = pb.start();
 545         } catch (IOException e) {
 546             throw new RuntimeException(
 547                     format("Couldn't start process '%s'", pb.command()), e);
 548         }
 549 
 550         String output;
 551         try {
 552             stdout = readAllBytes(p.getInputStream());
 553             stderr = readAllBytes(p.getErrorStream());
 554 
 555             output = toString(stdout, stderr);
 556         } catch (IOException e) {
 557             throw new RuntimeException(
 558                     format("Couldn't read process output '%s'", pb.command()), e);
 559         }
 560 
 561         try {
 562             p.waitFor();
 563         } catch (InterruptedException e) {
 564             throw new RuntimeException(
 565                     format("Process hasn't finished '%s'", pb.command()), e);
 566         }
 567         return new Result(p.exitValue(), stdout, stderr, output);
 568     }
 569 
 570     static final Path JAVA_HOME = Paths.get(System.getProperty("java.home"));
 571 
 572     static String getJDKTool(String name) {
 573         try {
 574             return JDKToolFinder.getJDKTool(name);
 575         } catch (Exception x) {
 576             Path j = JAVA_HOME.resolve("bin").resolve(name);
 577             if (Files.exists(j))
 578                 return j.toString();
 579             j = JAVA_HOME.resolve("..").resolve("bin").resolve(name);
 580             if (Files.exists(j))
 581                 return j.toString();
 582             throw new RuntimeException(x);
 583         }
 584     }
 585 
 586     static String toString(byte[] ba1, byte[] ba2) {
 587         return (new String(ba1, UTF_8)).concat(new String(ba2, UTF_8));
 588     }
 589 
 590     static class Result {
 591         final int exitValue;
 592         final byte[] stdout;
 593         final byte[] stderr;
 594         final String output;
 595 
 596         private Result(int exitValue, byte[] stdout, byte[] stderr, String output) {
 597             this.exitValue = exitValue;
 598             this.stdout = stdout;
 599             this.stderr = stderr;
 600             this.output = output;
 601         }
 602 
 603         InputStream stdoutAsStream() { return new ByteArrayInputStream(stdout); }
 604 
 605         Result assertSuccess() { assertTrue(exitValue == 0, output); return this; }
 606         Result assertFailure() { assertTrue(exitValue != 0, output); return this; }
 607 
 608         Result resultChecker(IOConsumer<Result> r) {
 609             try {  r.accept(this); return this; }
 610             catch (IOException x) { throw new UncheckedIOException(x); }
 611         }
 612 
 613         Result resultChecker(FailCheckerWithMessage c) { c.accept(this); return this; }
 614     }
 615 
 616     interface IOConsumer<T> { void accept(T t) throws IOException ;  }
 617 
 618     // readAllBytes implementation so the test can be run pre 1.9 ( legacyOnly )
 619     static byte[] readAllBytes(InputStream is) throws IOException {
 620         byte[] buf = new byte[8192];
 621         int capacity = buf.length;
 622         int nread = 0;
 623         int n;
 624         for (;;) {
 625             // read to EOF which may read more or less than initial buffer size
 626             while ((n = is.read(buf, nread, capacity - nread)) > 0)
 627                 nread += n;
 628 
 629             // if the last call to read returned -1, then we're done
 630             if (n < 0)
 631                 break;
 632 
 633             // need to allocate a larger buffer
 634             capacity = capacity << 1;
 635 
 636             buf = Arrays.copyOf(buf, capacity);
 637         }
 638         return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
 639     }
 640 
 641     // Standalone entry point for running with, possibly older, JDKs.
 642     public static void main(String[] args) throws Throwable {
 643         boolean legacyOnly = false;
 644         if (args.length != 0 && args[0].equals("legacyOnly"))
 645             legacyOnly = true;
 646 
 647         CLICompatibility test = new CLICompatibility(legacyOnly);
 648         for (Method m : CLICompatibility.class.getDeclaredMethods()) {
 649             if (m.getAnnotation(Test.class) != null) {
 650                 System.out.println("Invoking " + m.getName());
 651                 m.invoke(test);
 652             }
 653         }
 654     }
 655     CLICompatibility(boolean legacyOnly) { this.legacyOnly = legacyOnly; }
 656     CLICompatibility() { this.legacyOnly = false; }
 657 }