content: /*
   * Copyright 2016 Google Inc.
   *
   * Licensed 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 com.google.googlejavaformat.java;
  
  import static java.nio.charset.StandardCharsets.UTF_8;
  
  import com.google.common.base.CharMatcher;
  import com.google.common.base.Splitter;
  import com.google.common.collect.ImmutableRangeSet;
  import com.google.common.collect.Range;
  import java.io.IOException;
  import java.io.UncheckedIOException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  /** A parser for {@link CommandLineOptions}. */
  final class CommandLineOptionsParser {
  
    private static final Splitter COMMA_SPLITTER = Splitter.on(',');
    private static final Splitter COLON_SPLITTER = Splitter.on(':');
    private static final Splitter ARG_SPLITTER =
        Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults();
  
    /** Parses {@link CommandLineOptions}. */
    static CommandLineOptions parse(Iterable<String> options) {
      CommandLineOptions.Builder optionsBuilder = CommandLineOptions.builder();
      List<String> expandedOptions = new ArrayList<>();
      expandParamsFiles(options, expandedOptions);
      Iterator<String> it = expandedOptions.iterator();
      while (it.hasNext()) {
        String option = it.next();
        if (!option.startsWith("-")) {
          optionsBuilder.filesBuilder().add(option).addAll(it);
          break;
        }
        String flag;
        String value;
        int idx = option.indexOf('=');
        if (idx >= 0) {
          flag = option.substring(0, idx);
          value = option.substring(idx + 1);
        } else {
          flag = option;
          value = null;
        }
        // NOTE: update usage information in UsageException when new flags are added
        switch (flag) {
          case "-i":
          case "-r":
          case "-replace":
          case "--replace":
            optionsBuilder.inPlace(true);
            break;
          case "--lines":
          case "-lines":
          case "--line":
          case "-line":
            parseRangeSet(optionsBuilder.linesBuilder(), getValue(flag, it, value));
            break;
          case "--offset":
          case "-offset":
            optionsBuilder.addOffset(parseInteger(it, flag, value));
            break;
          case "--length":
          case "-length":
            optionsBuilder.addLength(parseInteger(it, flag, value));
            break;
          case "--aosp":
          case "-aosp":
          case "-a":
            optionsBuilder.aosp(true);
            break;
          case "--version":
          case "-version":
          case "-v":
            optionsBuilder.version(true);
            break;
          case "--help":
          case "-help":
          case "-h":
            optionsBuilder.help(true);
            break;
          case "--fix-imports-only":
            optionsBuilder.fixImportsOnly(true);
            break;
          case "--skip-sorting-imports":
            optionsBuilder.sortImports(false);
            break;
          case "--skip-removing-unused-imports":
            optionsBuilder.removeUnusedImports(false);
            break;
          case "--skip-reflowing-long-strings":
            optionsBuilder.reflowLongStrings(false);
            break;
          case "--skip-javadoc-formatting":
            optionsBuilder.formatJavadoc(false);
            break;
          case "-":
            optionsBuilder.stdin(true);
            break;
          case "-n":
          case "--dry-run":
            optionsBuilder.dryRun(true);
            break;
          case "--set-exit-if-changed":
            optionsBuilder.setExitIfChanged(true);
            break;
          case "-assume-filename":
          case "--assume-filename":
            optionsBuilder.assumeFilename(getValue(flag, it, value));
            break;
          default:
            throw new IllegalArgumentException("unexpected flag: " + flag);
        }
      }
      return optionsBuilder.build();
    }
  
    private static Integer parseInteger(Iterator<String> it, String flag, String value) {
      try {
        return Integer.valueOf(getValue(flag, it, value));
      } catch (NumberFormatException e) {
        throw new IllegalArgumentException(
            String.format("invalid integer value for %s: %s", flag, value), e);
      }
    }
  
    private static String getValue(String flag, Iterator<String> it, String value) {
      if (value != null) {
        return value;
      }
      if (!it.hasNext()) {
        throw new IllegalArgumentException("required value was not provided for: " + flag);
      }
      return it.next();
    }
  
    /**
     * Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be given
     * with multiple --lines flags or separated by commas. A single line can be set by a single
     * number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based numbering
     * used internally by google-java-format.
     */
    private static void parseRangeSet(ImmutableRangeSet.Builder<Integer> result, String ranges) {
      for (String range : COMMA_SPLITTER.split(ranges)) {
        result.add(parseRange(range));
      }
    }
  
    /**
     * Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are
     * converted here to {@code 0}-based.
     */
    private static Range<Integer> parseRange(String arg) {
      List<String> args = COLON_SPLITTER.splitToList(arg);
      switch (args.size()) {
        case 1:
          int line = Integer.parseInt(args.get(0)) - 1;
          return Range.closedOpen(line, line + 1);
        case 2:
          int line0 = Integer.parseInt(args.get(0)) - 1;
          int line1 = Integer.parseInt(args.get(1)) - 1;
          return Range.closedOpen(line0, line1 + 1);
        default:
          throw new IllegalArgumentException(arg);
      }
    }
  
    /**
     * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
     * the content of the file and appending whitespace-delimited options to {@code arguments}.
     */
    private static void expandParamsFiles(Iterable<String> args, List<String> expanded) {
      for (String arg : args) {
        if (arg.isEmpty()) {
          continue;
        }
        if (!arg.startsWith("@")) {
          expanded.add(arg);
        } else if (arg.startsWith("@@")) {
          expanded.add(arg.substring(1));
        } else {
          Path path = Paths.get(arg.substring(1));
          try {
            String sequence = new String(Files.readAllBytes(path), UTF_8);
            expandParamsFiles(ARG_SPLITTER.split(sequence), expanded);
          } catch (IOException e) {
            throw new UncheckedIOException(path + ": could not read file: " + e.getMessage(), e);
          }
        }
      }
    }
  }
  