CliCupInvoker.java

/*
 * Copyright 2020, R��gis D��camps
 * SPDX-License-Identifier: BSD-3-Clause
 */

package jflex.maven.plugin.cup;

import static com.google.common.base.Preconditions.checkArgument;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import java.io.File;
import java.util.ArrayList;
import java_cup.Main;
import org.apache.maven.plugin.logging.Log;

/** Wrapper around the dirty CUP API. */
class CliCupInvoker {

  private final Log log;

  CliCupInvoker(Log log) {
    this.log = log;
  }

  /**
   * Invokes CUP.
   *
   * @param javaPackage Specify that the parser and sym classes are to be placed in the named
   *     package. By default, no package specification is put in the generated code (hence the
   *     classes default to the special "unnamed" package).
   * @param outputDirectory Directory in which to output the generated Java Parser.
   * @param parserClassName Output parser and action code into a file (and class) with the given
   *     name instead of the default of "parser".
   * @param symClassName Output the symbol constant code into a class with the given name instead of
   *     the default of "sym".
   * @param symbolInterface Output the symbol constant code as an interface rather than as a class.
   * @param cupFileName input specification.
   */
  void invoke(
      String javaPackage,
      File outputDirectory,
      String parserClassName,
      String symClassName,
      boolean symbolInterface,
      String cupFileName)
      throws Exception {
    // CUP can only be invoked with main() method with CLI args.
    String[] args =
        buildArgv(
            javaPackage,
            outputDirectory,
            parserClassName,
            symClassName,
            symbolInterface,
            cupFileName);
    log.debug("cup " + Joiner.on(' ').join(args));
    Main.main(args);
  }

  @VisibleForTesting
  static String[] buildArgv(
      String javaPackage,
      File outputDirectory,
      String parserClassName,
      String symClassName,
      boolean symbolInterface,
      String cupFileName) {
    // It's a builder but it's easier to read the IllegalArgumentException if each arg is added on
    // its own line.
    ArgBuilder args = new ArgBuilder();
    if (!Strings.isNullOrEmpty(javaPackage)) {
      args.addOption("package", javaPackage);
    }
    args.addOption("destdir", outputDirectory.getAbsolutePath());
    args.addOption("parser", parserClassName);
    args.addOption("symbols", symClassName);
    if (symbolInterface) {
      args.addOption("interface");
    }
    // inputFile
    args.addArg(cupFileName);
    return args.buildArray();
  }

  private static class ArgBuilder {

    private final ArrayList<String> args = new ArrayList<>();

    ArgBuilder addArg(String argument) {
      args.add(argument);
      return this;
    }

    ArgBuilder addOption(String optionName) {
      checkArgument(!Strings.isNullOrEmpty(optionName), "Option name cannot be empty");
      args.add("-" + optionName);
      return this;
    }

    ArgBuilder addOption(String optionName, String optionValue) {
      checkArgument(!Strings.isNullOrEmpty(optionName), "Option name cannot be empty");
      checkArgument(
          !Strings.isNullOrEmpty(optionValue),
          String.format("Value for option -%s should not be empty", optionName));
      args.add("-" + optionName);
      args.add(optionValue);
      return this;
    }

    String[] buildArray() {
      return args.toArray(new String[args.size()]);
    }
  }
}