1 /*
   2  * Copyright (c) 1999, 2018, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.jpackage.main;
  27 
  28 import java.io.FileNotFoundException;
  29 import java.io.IOException;
  30 import java.io.File;
  31 import java.io.Reader;
  32 import java.nio.charset.Charset;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.nio.file.Paths;
  36 import java.util.ArrayList;
  37 import java.util.Arrays;
  38 import java.util.List;
  39 
  40 /**
  41  * This file was originally a copy of CommandLine.java in
  42  * com.sun.tools.javac.main.
  43  * It should track changes made to that file.
  44  */
  45 
  46 /**
  47  * Various utility methods for processing Java tool command line arguments.
  48  *
  49  *  <p><b>This is NOT part of any supported API.
  50  *  If you write code that depends on this, you do so at your own risk.
  51  *  This code and its internal interfaces are subject to change or
  52  *  deletion without notice.</b>
  53  */
  54 class CommandLine {
  55     /**
  56      * Process Win32-style command files for the specified command line
  57      * arguments and return the resulting arguments. A command file argument
  58      * is of the form '@file' where 'file' is the name of the file whose
  59      * contents are to be parsed for additional arguments. The contents of
  60      * the command file are parsed using StreamTokenizer and the original
  61      * '@file' argument replaced with the resulting tokens. Recursive command
  62      * files are not supported. The '@' character itself can be quoted with
  63      * the sequence '@@'.
  64      * @param args the arguments that may contain @files
  65      * @return the arguments, with @files expanded
  66      * @throws IOException if there is a problem reading any of the @files
  67      */
  68     public static String[] parse(String[] args) throws IOException {
  69         List<String> newArgs = new ArrayList<>();
  70         appendParsedCommandArgs(newArgs, Arrays.asList(args));
  71         return newArgs.toArray(new String[newArgs.size()]);
  72     }
  73 
  74     private static void appendParsedCommandArgs(List<String> newArgs,
  75             List<String> args) throws IOException {
  76         for (String arg : args) {
  77             if (arg.length() > 1 && arg.charAt(0) == '@') {
  78                 arg = arg.substring(1);
  79                 if (arg.charAt(0) == '@') {
  80                     newArgs.add(arg);
  81                 } else {
  82                     loadCmdFile(arg, newArgs);
  83                 }
  84             } else {
  85                 newArgs.add(arg);
  86             }
  87         }
  88     }
  89 
  90     private static void loadCmdFile(String name, List<String> args)
  91             throws IOException {
  92         if (!Files.isReadable(Path.of(name))) {
  93             throw new FileNotFoundException(name);
  94         }
  95         try (Reader r = Files.newBufferedReader(Paths.get(name),
  96                 Charset.defaultCharset())) {
  97             Tokenizer t = new Tokenizer(r);
  98             String s;
  99             while ((s = t.nextToken()) != null) {
 100                 args.add(s);
 101             }
 102         }
 103     }
 104 
 105     public static class Tokenizer {
 106         private final Reader in;
 107         private int ch;
 108 
 109         public Tokenizer(Reader in) throws IOException {
 110             this.in = in;
 111             ch = in.read();
 112         }
 113 
 114         public String nextToken() throws IOException {
 115             skipWhite();
 116             if (ch == -1) {
 117                 return null;
 118             }
 119 
 120             StringBuilder sb = new StringBuilder();
 121             char quoteChar = 0;
 122 
 123             while (ch != -1) {
 124                 switch (ch) {
 125                     case ' ':
 126                     case '\t':
 127                     case '\f':
 128                         if (quoteChar == 0) {
 129                             return sb.toString();
 130                         }
 131                         sb.append((char) ch);
 132                         break;
 133 
 134                     case '\n':
 135                     case '\r':
 136                         return sb.toString();
 137 
 138                     case '\'':
 139                     case '"':
 140                         if (quoteChar == 0) {
 141                             quoteChar = (char) ch;
 142                         } else if (quoteChar == ch) {
 143                             quoteChar = 0;
 144                         } else {
 145                             sb.append((char) ch);
 146                         }
 147                         break;
 148 
 149                     case '\\':
 150                         if (quoteChar != 0) {
 151                             ch = in.read();
 152                             switch (ch) {
 153                                 case '\n':
 154                                 case '\r':
 155                                     while (ch == ' ' || ch == '\n'
 156                                             || ch == '\r' || ch == '\t'
 157                                             || ch == '\f') {
 158                                         ch = in.read();
 159                                     }
 160                                     continue;
 161 
 162                                 case 'n':
 163                                     ch = '\n';
 164                                     break;
 165                                 case 'r':
 166                                     ch = '\r';
 167                                     break;
 168                                 case 't':
 169                                     ch = '\t';
 170                                     break;
 171                                 case 'f':
 172                                     ch = '\f';
 173                                     break;
 174                             }
 175                         }
 176                         sb.append((char) ch);
 177                         break;
 178 
 179                     default:
 180                         sb.append((char) ch);
 181                 }
 182 
 183                 ch = in.read();
 184             }
 185 
 186             return sb.toString();
 187         }
 188 
 189         void skipWhite() throws IOException {
 190             while (ch != -1) {
 191                 switch (ch) {
 192                     case ' ':
 193                     case '\t':
 194                     case '\n':
 195                     case '\r':
 196                     case '\f':
 197                         break;
 198 
 199                     case '#':
 200                         ch = in.read();
 201                         while (ch != '\n' && ch != '\r' && ch != -1) {
 202                             ch = in.read();
 203                         }
 204                         break;
 205 
 206                     default:
 207                         return;
 208                 }
 209 
 210                 ch = in.read();
 211             }
 212         }
 213     }
 214 }