All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.obolibrary.robot.CommandLineHelper Maven / Gradle / Ivy

Go to download

Command-line interface for ROBOT: Commands for working with OWL ontologies, especially Open Biological and Biomedical Ontologes (OBO).

There is a newer version: 1.9.7
Show newest version
package org.obolibrary.robot;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import com.github.jsonldjava.core.Context;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import org.apache.commons.cli.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geneontology.reasoner.ExpressionMaterializingReasonerFactory;
import org.geneontology.whelk.owlapi.WhelkOWLReasonerFactory;
import org.semanticweb.elk.owlapi.ElkReasonerFactory;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.slf4j.LoggerFactory;
import uk.ac.manchester.cs.jfact.JFactFactory;

/** Convenience methods for working with command line options. */
public class CommandLineHelper {
  /** Logger. */
  private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CommandLineHelper.class);

  /** Namespace for general input error messages. */
  private static final String NS = "errors#";

  /** Error message when a boolean value is not "true" or "false". Expects option name. */
  private static final String booleanValueError =
      NS + "BOOLEAN VALUE ERROR arg for %s must be true or false";

  /** Error message when --input is provided in a chained command. */
  private static final String chainedInputError =
      NS + "CHAINED INPUT ERROR do not use an --input option for chained commands";

  /** Error message when an invalid IRI is provided. Expects the entry field and term. */
  private static final String invalidIRIError =
      NS + "INVALID IRI ERROR %1$s \"%2$s\" is not a valid CURIE or IRI";

  /** Error message when user provides an invalid reasoner. Expects reasonerName in formatting. */
  private static final String invalidReasonerError =
      NS + "INVALID REASONER ERROR unknown reasoner: %s";

  /** Error message when no input ontology is provided for methods that accept multiple inputs. */
  private static final String missingRequirementError = NS + "MISSING REQUIREMENT ERROR %s";

  /** Error message when no input ontology is provided. */
  private static final String missingInputError = NS + "MISSING INPUT ERROR an --input is required";

  /** Error message when no input ontology is provided for methods that accept multiple inputs. */
  private static final String missingInputsError =
      NS + "MISSING INPUT ERROR at least one --input is required";

  /** Error message when input terms are not provided. */
  private static final String missingTermsError =
      NS + "MISSING TERMS ERROR term(s) are required with --term or --term-file";

  /** Error message when more than one --input is specified, excluding merge and unmerge. */
  private static final String multipleInputsError =
      NS + "MULITPLE INPUTS ERROR only one --input is allowed";

  /** Error message when the --inputs pattern does not include * or ?, or is not quoted */
  private static final String wildcardError =
      NS + "WILDCARD ERROR --inputs argument must be a quoted wildcard pattern";

  /**
   * Given a single string, return a list of strings split at whitespace but allowing for quoted
   * values, as a command-line parser does.
   *
   * @param toProcess the string to parse
   * @return a string list, split at whitespace, with quotation marks removed
   * @throws Exception on parsing problems
   */
  public static List parseArgList(String toProcess) throws Exception {
    return new ArrayList<>(Arrays.asList(parseArgs(toProcess)));
  }

  /**
   * Given a single string, return an array of strings split at whitespace but allowing for quoted
   * values, as a command-line parser does. Adapted from org.apache.tools.ant.types.Commandline
   *
   * @param toProcess the string to parse
   * @return a string array, split at whitespace, with quotation marks removed
   * @throws Exception on parsing problems
   */
  public static String[] parseArgs(String toProcess) throws Exception {
    if (toProcess == null || toProcess.length() == 0) {
      return new String[0];
    }

    // parse with a simple finite state machine
    final int normal = 0;
    final int inQuote = 1;
    final int inDoubleQuote = 2;
    int state = normal;
    StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
    Vector v = new Vector<>();
    StringBuffer current = new StringBuffer();
    boolean lastTokenHasBeenQuoted = false;

    while (tok.hasMoreTokens()) {
      String nextTok = tok.nextToken();
      switch (state) {
        case inQuote:
          if ("\'".equals(nextTok)) {
            lastTokenHasBeenQuoted = true;
            state = normal;
          } else {
            current.append(nextTok);
          }
          break;
        case inDoubleQuote:
          if ("\"".equals(nextTok)) {
            lastTokenHasBeenQuoted = true;
            state = normal;
          } else {
            current.append(nextTok);
          }
          break;
        default:
          if ("\'".equals(nextTok)) {
            state = inQuote;
          } else if ("\"".equals(nextTok)) {
            state = inDoubleQuote;
          } else if (" ".equals(nextTok)) {
            if (lastTokenHasBeenQuoted || current.length() != 0) {
              v.addElement(current.toString());
              current = new StringBuffer();
            }
          } else {
            current.append(nextTok);
          }
          lastTokenHasBeenQuoted = false;
          break;
      }
    }

    if (lastTokenHasBeenQuoted || current.length() != 0) {
      v.addElement(current.toString());
    }

    if (state == inQuote || state == inDoubleQuote) {
      throw new Exception("unbalanced quotes in " + toProcess);
    }

    String[] args = new String[v.size()];
    v.copyInto(args);
    return args;
  }

  /**
   * Given a command line, return the values for an option as a list. This is just a convenience
   * function for turning an array into a list.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @return the option values as a list of strings, maybe empty
   */
  public static List getOptionValues(CommandLine line, String name) {
    List valueList = new ArrayList<>();
    String[] valueArray = line.getOptionValues(name);
    if (valueArray != null) {
      valueList = new ArrayList<>(Arrays.asList(valueArray));
    }
    return valueList;
  }

  /**
   * Given a command line and an index, return the argument at that index.
   *
   * @param line the command line to use
   * @param index the index of the argument
   * @return the argument at the given index
   */
  public static String getIndexValue(CommandLine line, int index) {
    String result = null;
    List arguments = Arrays.asList(line.getArgs());
    if (arguments.size() > index) {
      result = arguments.get(index);
    }
    return result;
  }

  /**
   * Given a command line, return its first argument, which should be the name of the command to
   * execute.
   *
   * @param line the command line to use
   * @return the name of the command to execute
   */
  public static String getCommand(CommandLine line) {
    return getIndexValue(line, 0);
  }

  /**
   * Return true if a command line include the option with the given name, or the command with the
   * given name.
   *
   * @param line the command line to use
   * @param name the name of the flag or argument to check for
   * @return true if a flag or first argument match the given name, false otherwise
   */
  public static boolean hasFlagOrCommand(CommandLine line, String name) {
    if (line.hasOption(name)) {
      return true;
    }
    String command = getCommand(line);
    return command != null && command.equals(name);
  }

  /**
   * Given a command line, get the 'axioms' option(s) and make sure all are properly split and
   * return one axiom selector per list entry.
   *
   * @param line the command line to use
   * @return cleaned list of input axiom type strings
   */
  public static List cleanAxiomStrings(CommandLine line) {
    List axiomTypeStrings = getOptionalValues(line, "axioms");

    if (axiomTypeStrings.isEmpty()) {
      axiomTypeStrings.add("all");
    }

    // Split if it's one arg with spaces
    List axiomTypeFixedStrings = new ArrayList<>();
    for (String axiom : axiomTypeStrings) {
      if (axiom.contains(" ")) {
        axiomTypeFixedStrings.addAll(Arrays.asList(axiom.split(" ")));
      } else {
        axiomTypeFixedStrings.add(axiom);
      }
    }

    return axiomTypeFixedStrings;
  }

  /**
   * Given a command line and an IOHelper, return a list of base namespaces from the '--base-iri'
   * option.
   *
   * @param line the command line to use
   * @param ioHelper the IOHelper to resolve prefixes
   * @return list of full base namespaces
   */
  public static List getBaseNamespaces(CommandLine line, IOHelper ioHelper) {
    List bases = new ArrayList<>();
    if (!line.hasOption("base-iri")) {
      return bases;
    }

    Map prefixMap = ioHelper.getPrefixes();
    for (String base : line.getOptionValues("base-iri")) {
      if (!base.contains(":")) {
        String expanded = prefixMap.getOrDefault(base, null);
        if (expanded != null) {
          bases.add(expanded);
        } else {
          logger.error(String.format("Unknown prefix: '%s'", base));
        }
      } else {
        bases.add(base);
      }
    }

    return bases;
  }

  /**
   * Given a command line, an argument name, the boolean default value, and boolean if the arg is
   * optional, return the value of the command-line option 'name'.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @param defaultValue the default value to use if the option is not provided
   * @param optionalArg if true, the option without an arg will return true
   * @return the option value as boolean, or the default if not found
   */
  public static boolean getBooleanValue(
      CommandLine line, String name, boolean defaultValue, boolean optionalArg) {
    if (line.hasOption(name)) {
      if (CommandLineHelper.getOptionalValue(line, name) == null) {
        return true;
      } else {
        return CommandLineHelper.getBooleanValue(line, name, defaultValue);
      }
    } else {
      return false;
    }
  }

  /**
   * Given a command line, return the value of --axioms as a set of classes that extend OWLAxiom.
   *
   * @deprecated split into methods {@link #cleanAxiomStrings(CommandLine)} and others in {@link
   *     org.obolibrary.robot.RelatedObjectsHelper}
   * @param line the command line to use
   * @return set of OWLAxiom types
   */
  @Deprecated
  public static Set> getAxiomValues(CommandLine line) {
    Set> axiomTypes = new HashSet<>();
    List axiomTypeStrings = cleanAxiomStrings(line);
    // Then get the actual types
    for (String axiom : axiomTypeStrings) {
      Set> addTypes = RelatedObjectsHelper.getAxiomValues(axiom);
      if (addTypes != null) {
        axiomTypes.addAll(addTypes);
      }
    }
    return axiomTypes;
  }

  /**
   * Get the boolean value of a command-line option with the given name.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @param defaultValue the default value to use
   * @return the option value as boolean, or the default if not found
   */
  public static boolean getBooleanValue(CommandLine line, String name, boolean defaultValue) {
    String val = getDefaultValue(line, name, String.valueOf(defaultValue));
    if ("true".equals(val)) {
      return true;
    } else if ("false".equals(val)) {
      return false;
    } else {
      throw new IllegalArgumentException(String.format(booleanValueError, name));
    }
  }

  /**
   * Get the value of the command-line option with the given name.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @return the option value as a string, or null if not found
   */
  public static String getOptionalValue(CommandLine line, String name) {
    return getDefaultValue(line, name, null);
  }

  /**
   * Get the value of the command-line options with the given name.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @return the option value as a list of strings, maybe empty
   */
  public static List getOptionalValues(CommandLine line, String name) {
    if (line.hasOption(name)) {
      return new ArrayList<>(Arrays.asList(line.getOptionValues(name)));
    } else {
      return new ArrayList<>();
    }
  }

  /**
   * Get the value of the command-line option with the given name, or return a default value if the
   * option is not found.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @param defaultValue the default value to use
   * @return the option value as a string, or the default if the option not found
   */
  public static String getDefaultValue(CommandLine line, String name, String defaultValue) {
    String result = defaultValue;
    if (line.hasOption(name)) {
      // Only one arg should be passed per option
      result = line.getOptionValue(name);
    }
    return result;
  }

  /**
   * Get the value of the command-line option with the given name, or throw an
   * IllegalArgumentException with a given message if the option is not found.
   *
   * @param line the command line to use
   * @param name the name of the option to find
   * @param message the message for the exception
   * @return the option value as a string
   * @throws IllegalArgumentException if the option is not found
   */
  public static String getRequiredValue(CommandLine line, String name, String message)
      throws IllegalArgumentException {
    String result = getOptionalValue(line, name);
    if (result == null) {
      throw new IllegalArgumentException(String.format(missingRequirementError, message));
    }
    return result;
  }

  /**
   * Given a command line, return an initialized IOHelper. The --prefix, --add-prefix, --prefixes,
   * --add-prefixes, --noprefixes, --xml-entities, and --base options are handled.
   *
   * @param line the command line to use
   * @return an initialized IOHelper
   * @throws IOException on issue creating IOHelper with given context
   */
  public static IOHelper getIOHelper(CommandLine line) throws IOException {
    IOHelper ioHelper;
    String prefixes = getOptionalValue(line, "prefixes");
    if (prefixes != null) {
      ioHelper = new IOHelper(prefixes);
    } else {
      ioHelper = new IOHelper(!line.hasOption("noprefixes"));
    }
    prefixes = getOptionalValue(line, "add-prefixes");
    if (prefixes != null) {
      ioHelper.addPrefixes(prefixes);
    }

    for (String prefix : getOptionalValues(line, "prefix")) {
      ioHelper.addPrefix(prefix);
    }

    for (String prefix : getOptionalValues(line, "add-prefix")) {
      ioHelper.addPrefix(prefix);
    }

    ioHelper.setXMLEntityFlag(line.hasOption("xml-entities"));
    ioHelper.setStrict(line.hasOption("strict"));

    return ioHelper;
  }

  /**
   * Given an IOHelper and a command line, check for required options and return a loaded input
   * ontology. Currently handles --input and --input-iri options.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param line the command line to use
   * @return the input ontology
   * @throws IllegalArgumentException if requires options are missing
   * @throws IOException if the ontology cannot be loaded
   */
  public static OWLOntology getInputOntology(IOHelper ioHelper, CommandLine line)
      throws IllegalArgumentException, IOException {
    // Get the catalog if specified by --catalog
    String catalogPath = getOptionalValue(line, "catalog");
    return getInputOntology(ioHelper, line, catalogPath);
  }

  /**
   * Given an IOHelper, a command line, and a path to a catalog, return an OWLOntology loaded from
   * input or input-iri using the specified catalog (or null).
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param line the command line to use
   * @param catalogPath the catalog to use to load imports
   * @return the input ontology
   * @throws IllegalArgumentException if requires options are missing
   * @throws IOException if the ontology cannot be loaded
   */
  public static OWLOntology getInputOntology(
      IOHelper ioHelper, CommandLine line, String catalogPath)
      throws IllegalArgumentException, IOException {
    List inputOntologyPaths = getOptionalValues(line, "input");
    List inputOntologyIRIs = getOptionalValues(line, "input-iri");

    int check = inputOntologyPaths.size() + inputOntologyIRIs.size();
    if (check > 1) {
      throw new IllegalArgumentException(multipleInputsError);
    }

    if (!inputOntologyPaths.isEmpty()) {
      if (catalogPath != null) {
        return ioHelper.loadOntology(inputOntologyPaths.get(0), catalogPath);
      } else {
        return ioHelper.loadOntology(inputOntologyPaths.get(0));
      }
    } else if (!inputOntologyIRIs.isEmpty()) {
      if (catalogPath != null) {
        return ioHelper.loadOntology(IRI.create(inputOntologyIRIs.get(0)), catalogPath);
      } else {
        return ioHelper.loadOntology(IRI.create(inputOntologyIRIs.get(0)));
      }
    } else {
      // Both input options are empty
      throw new IllegalArgumentException(missingInputError);
    }
  }

  /**
   * Given an IOHelper and a command line, check for required options and return a list of loaded
   * input ontologies. Currently handles --input, --input-iri, and --inputs options.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param line the command line to use
   * @param allowEmpty if an empty list may be returned (when chaining commands, an input was
   *     already provided)
   * @return the list of input ontologies
   * @throws IllegalArgumentException if requires options are missing
   * @throws IOException if the ontology cannot be loaded
   */
  public static List getInputOntologies(
      IOHelper ioHelper, CommandLine line, boolean allowEmpty)
      throws IllegalArgumentException, IOException {
    List inputOntologies = new ArrayList<>();
    String catalogPath = getOptionalValue(line, "catalog");

    if (catalogPath != null) {
      inputOntologies.addAll(getInputOntologies(ioHelper, line, catalogPath));
    } else {
      inputOntologies.addAll(getInputOntologies(ioHelper, line));
    }

    if (inputOntologies.isEmpty() && !allowEmpty) {
      throw new IllegalArgumentException(missingInputsError);
    }

    return inputOntologies;
  }

  /**
   * Given an IOHelper, a state object, and a command line, update the state with the ontology.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param state the input state, maybe null
   * @param line the command line to use
   * @return the updated state
   * @throws IllegalArgumentException if requires options are missing
   */
  public static CommandState updateInputOntology(
      IOHelper ioHelper, CommandState state, CommandLine line) throws IllegalArgumentException {
    return updateInputOntology(ioHelper, state, line, true);
  }

  /**
   * Given an IOHelper, a state object, a command line, and a "required" flag, update the state with
   * the ontology. If the state contains an ontology, use it. If the state is null or does not
   * contain an ontology, use the `--input` option. If the state has an ontology and there's an
   * `--input` throw an exception warning the use to use two commands instead of a chain of
   * commands.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param state the input state, maybe null
   * @param line the command line to use
   * @param required when true, throw an exception if ontology is not found
   * @return the updated state
   * @throws IllegalArgumentException if requires options are missing
   */
  public static CommandState updateInputOntology(
      IOHelper ioHelper, CommandState state, CommandLine line, boolean required)
      throws IllegalArgumentException {
    if (state != null && state.getOntology() != null) {
      if (line.hasOption("input") || line.hasOption("input-IRI")) {
        throw new IllegalArgumentException(chainedInputError);
      } else {
        return state;
      }
    }

    // If normal --input is used, save the path
    String ontologyPath = CommandLineHelper.getOptionalValue(line, "input");
    if (ontologyPath != null) {
      state.setOntologyPath(ontologyPath);
    }

    // If a --catalog is provided, save the catalog
    String catalogPath = CommandLineHelper.getOptionalValue(line, "catalog");
    if (catalogPath != null) {
      state.setCatalogPath(catalogPath);
    }

    OWLOntology ontology = null;
    try {
      ontology = getInputOntology(ioHelper, line, catalogPath);
    } catch (Exception e) {
      if (required) {
        // Throw message from IOHelper
        throw new IllegalArgumentException(e);
      }
    }

    if (state == null) {
      state = new CommandState();
    }
    state.setOntology(ontology);
    return state;
  }

  /**
   * Given a command line, check for the required options and return a File for saving data.
   *
   * @param line the command line to use
   * @return the File for output; may be null; may not exist!
   * @throws IllegalArgumentException if required options are not found
   */
  public static File getOutputFile(CommandLine line) throws IllegalArgumentException {
    String outputPath = getOptionalValue(line, "output");
    File outputFile = null;
    if (outputPath != null) {
      outputFile = new File(outputPath);
    }
    return outputFile;
  }

  /**
   * Given a command line, check for the required options and return an IRI to be used as the
   * OntologyIRI for the output ontology.
   *
   * @param line the command line to use
   * @return the IRI for the output ontology, or null
   */
  public static IRI getOutputIRI(CommandLine line) {
    String outputIRIString = getOptionalValue(line, "output-iri");
    IRI outputIRI = null;
    if (outputIRIString != null) {
      outputIRI = IRI.create(outputIRIString);
    }
    return outputIRI;
  }

  /**
   * Given a command line and an ontology, for each `--output` option (if any), save a copy of the
   * ontology to the specified path.
   *
   * @param line the command lien to use
   * @param ontology the ontology to save
   * @throws IOException on any problem
   */
  public static void maybeSaveOutput(CommandLine line, OWLOntology ontology) throws IOException {
    IOHelper ioHelper = getIOHelper(line);
    // Determine if OBO structure should be enforced
    boolean checkOBO = CommandLineHelper.getBooleanValue(line, "check", true);

    // Determine if prefixes should be added to the header of output
    // Create a map of these to include in output (or an empty map)
    Map addPrefixes = getAddPrefixes(line);

    // Get an output format or null
    // If null, format will be guessed from the output path
    String format = CommandLineHelper.getOptionalValue(line, "format");
    OWLDocumentFormat df;
    if (format != null) {
      df = IOHelper.getFormat(format);
    } else {
      df = null;
    }

    // Save outputs
    for (String path : getOptionValues(line, "output")) {
      try {
        // maybe guess the document format
        OWLDocumentFormat thisDf = df;
        if (thisDf == null) {
          if (path.endsWith(".gz")) {
            path = path.substring(0, path.lastIndexOf("."));
          }
          String formatName = FilenameUtils.getExtension(path);
          thisDf = IOHelper.getFormat(formatName);
        }
        ioHelper.saveOntology(ontology, thisDf, IRI.create(new File(path)), addPrefixes, checkOBO);
      } catch (IllegalArgumentException e) {
        // Exception from getFormat -- invalid format
        throw new IllegalArgumentException(
            String.format(IOHelper.invalidFormatError, path.substring(path.lastIndexOf(".") + 1)),
            e);
      }
    }
  }

  /**
   * Try to create an IRI from a string input. If the term is not in a valid format (null), an
   * IllegalArgumentException is thrown to prevent null from being passed into other methods.
   *
   * @param ioHelper IOHelper to use
   * @param term the term to convert to an IRI
   * @param field the field in which the term was entered, for reporting
   * @return the new IRI if successful
   */
  public static IRI maybeCreateIRI(IOHelper ioHelper, String term, String field) {
    IRI iri = ioHelper.createIRI(term);
    if (iri == null) {
      throw new IllegalArgumentException(String.format(invalidIRIError, field, term));
    }
    return iri;
  }

  /**
   * Given a command line, get a map of all prefixes to use and add to the output.
   *
   * @param line the command line to use
   * @return a map of prefixes to add to output
   * @throws IOException if the prefixes are not formatted correctly or a JSON file cannot be read
   */
  public static Map getAddPrefixes(CommandLine line) throws IOException {
    Map addPrefixes = new HashMap<>();
    if (line.hasOption("add-prefix")) {
      for (String pref : CommandLineHelper.getOptionalValues(line, "add-prefix")) {
        String[] split = pref.split(": ");
        if (split.length != 2) {
          throw new IOException(String.format(IOHelper.invalidPrefixError, pref));
        }
        addPrefixes.put(split[0], split[1]);
      }
    }
    if (line.hasOption("add-prefixes")) {
      for (String prefixFilePath : CommandLineHelper.getOptionalValues(line, "add-prefixes")) {
        File prefixFile = new File(prefixFilePath);
        if (!prefixFile.exists()) {
          throw new IOException(String.format(IOHelper.fileDoesNotExistError, prefixFilePath));
        }
        Context json =
            IOHelper.parseContext(FileUtils.readFileToString(prefixFile, Charset.defaultCharset()));
        addPrefixes.putAll(json.getPrefixes(false));
      }
    }
    return addPrefixes;
  }

  /**
   * Given an IOHelper and a command line, check for the required options and return a set of IRIs
   * for terms. Handles --terms and --term-file options.
   *
   * @param ioHelper the IOHelper to use for loading the terms
   * @param line the command line to use
   * @return a set of term IRIs
   * @throws IllegalArgumentException if the required options are not found
   * @throws IOException if the term file cannot be loaded
   */
  public static Set getTerms(IOHelper ioHelper, CommandLine line)
      throws IllegalArgumentException, IOException {
    return getTerms(ioHelper, line, false);
  }

  /**
   * As getTerms, but allow the list to be empty.
   *
   * @param ioHelper the IOHelper to use for loading the terms
   * @param line the command line to use
   * @param allowEmpty true if empty lists of properties are allowed
   * @return a set of term IRIs
   * @throws IllegalArgumentException if the required options are not found
   * @throws IOException if the term file cannot be loaded
   */
  public static Set getTerms(IOHelper ioHelper, CommandLine line, boolean allowEmpty)
      throws IllegalArgumentException, IOException {
    Set terms = getTerms(ioHelper, line, "term", "term-file");

    if (terms.size() == 0 && !allowEmpty) {
      throw new IllegalArgumentException(missingTermsError);
    }
    return terms;
  }

  /**
   * Given an IOHelper and a command line, and the names of two options, check for the required
   * options and return a set of IRIs for terms. Handles single term options and term-file options.
   * Allows empty returns.
   *
   * @param ioHelper the IOHelper to use for loading the terms
   * @param line the command line to use
   * @param singles the option name for single terms, or null
   * @param paths the option name for term file paths, or null
   * @return a set of term IRIs
   * @throws IllegalArgumentException if the required options are not found
   * @throws IOException if the term file cannot be loaded
   */
  public static Set getTerms(IOHelper ioHelper, CommandLine line, String singles, String paths)
      throws IllegalArgumentException, IOException {
    Set termStrings = new HashSet<>();
    if (singles != null) {
      termStrings.addAll(getOptionValues(line, singles));
    }
    if (paths != null) {
      for (String path : getOptionValues(line, paths)) {
        termStrings.add(FileUtils.readFileToString(new File(path), Charset.defaultCharset()));
      }
    }

    Set terms = new HashSet<>();
    for (String termString : termStrings) {
      terms.addAll(ioHelper.parseTerms(termString));
    }

    return terms;
  }

  /**
   * Given a string of a reasoner name from user input, return the reasoner factory. If the user
   * input is not valid, throw IllegalArgumentExcepiton. By default, EMR is not allowed.
   *
   * @param line the command line to use
   * @return OWLReasonerFactory if successful
   */
  public static OWLReasonerFactory getReasonerFactory(CommandLine line) {
    return getReasonerFactory(line, false);
  }

  /**
   * Given a string of a reasoner name from user input, return the reasoner factory. If the user
   * input is not valid, throw IllegalArgumentExcepiton.
   *
   * @param line the command line to use
   * @param allowEMR boolean specifying if EMR can be returned
   * @return OWLReasonerFactory if successful
   */
  public static OWLReasonerFactory getReasonerFactory(CommandLine line, boolean allowEMR) {
    // ELK is the default reasoner
    String reasonerName = getDefaultValue(line, "reasoner", "ELK").trim().toLowerCase();
    logger.info("Reasoner: " + reasonerName);

    if (reasonerName.equals("structural")) {
      return new org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory();
    } else if (reasonerName.equals("hermit")) {
      return new org.semanticweb.HermiT.ReasonerFactory();
    } else if (reasonerName.equals("jfact")) {
      return new JFactFactory();
      // Reason must change behavior with EMR, so not all commands can use it
    } else if (reasonerName.equals("emr") && allowEMR) {
      ElkReasonerFactory innerReasonerFactory = new org.semanticweb.elk.owlapi.ElkReasonerFactory();
      return new ExpressionMaterializingReasonerFactory(innerReasonerFactory);
    } else if (reasonerName.equals("elk")) {
      return new org.semanticweb.elk.owlapi.ElkReasonerFactory();
    } else if (reasonerName.equals("whelk")) {
      return new WhelkOWLReasonerFactory();
    } else {
      throw new IllegalArgumentException(String.format(invalidReasonerError, reasonerName));
    }
  }

  /**
   * Print a help message for a command.
   *
   * @param usage the usage information for the command
   * @param options the command line options for the command
   */
  public static void printHelp(String usage, Options options) {
    HelpFormatter formatter = new HelpFormatter();
    formatter.printHelp(usage, options);
  }

  /**
   * Print the ROBOT version
   *
   * @throws IOException on issue getting info from JAR
   */
  public static void printVersion() throws IOException {
    Properties p = new Properties();
    // The resource can be accessed from the class, except when running as a JAR
    URL resource =
        CommandLineHelper.class
            .getClassLoader()
            .getResource("/META-INF/maven/org.obolibrary.robot/robot-command/pom.properties");
    if (resource != null) {
      URI uri;
      try {
        uri = resource.toURI();
      } catch (URISyntaxException e) {
        throw new IOException(e);
      }
      File f = new File(uri);
      InputStream is = new FileInputStream(f);
      p.load(is);
    } else {
      // Brute-force to get properties file from JAR
      // This will be used any time `robot --version` is entered on command line
      String cls = CommandLineHelper.class.getName().replace(".", "/") + ".class";
      resource = CommandLineHelper.class.getClassLoader().getResource(cls);
      if (resource == null) {
        throw new IOException(
            "Cannot access version information from JAR. The resource does not exist.");
      }
      if (resource.getProtocol().equals("jar")) {
        // Get the JAR path and open as JAR file
        String jarPath = resource.getPath().substring(5, resource.getPath().indexOf("!"));
        try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"))) {
          ZipEntry entry =
              jar.getEntry("META-INF/maven/org.obolibrary.robot/robot-command/pom.properties");
          if (entry != null) {
            InputStream is = jar.getInputStream(entry);
            p.load(is);
          } else {
            throw new IOException(
                "Cannot access version information from JAR. The properties file does not exist.");
          }
        }
      }
    }
    String version = p.getProperty("version");
    System.out.println("ROBOT version " + version);
  }

  /**
   * Create a new Options object with shared options for 'help' and 'version'.
   *
   * @return a new Options object with some options added
   */
  public static Options getCommonOptions() {
    Options o = new Options();
    o.addOption("h", "help", false, "print usage information");
    o.addOption("V", "version", false, "print version information");
    o.addOption("v", "verbose", false, "increased logging");
    o.addOption("vv", "very-verbose", false, "high logging");
    o.addOption("vvv", "very-very-verbose", false, "maximum logging, including stack traces");
    o.addOption(null, "catalog", true, "use catalog from provided file");
    o.addOption("p", "prefix", true, "add a prefix 'foo: http://bar'");
    o.addOption("P", "prefixes", true, "use prefixes from JSON-LD file");
    o.addOption(null, "noprefixes", false, "do not use default prefixes");
    o.addOption(null, "add-prefix", true, "add prefix 'foo: http://bar' to the output");
    o.addOption(null, "add-prefixes", true, "add JSON-LD prefixes to the output");
    o.addOption("x", "xml-entities", false, "use entity substitution with ontology XML output");
    o.addOption(null, "strict", false, "use strict parsing when loading an ontology");
    return o;
  }

  /**
   * Parse the command line, handle help and other common options, and return null or a CommandLine.
   *
   * @param usage the usage string for this command
   * @param options the command-line options for this command
   * @param args the command-line arguments provided
   * @param stopAtNonOption same as CommandLineParser
   * @return a new CommandLine object or null
   * @throws ParseException if the arguments cannot be parsed
   * @throws IOException on issue printing version
   */
  public static CommandLine maybeGetCommandLine(
      String usage, Options options, String[] args, boolean stopAtNonOption)
      throws ParseException, IOException {
    CommandLineParser parser = new DefaultParser();
    CommandLine line = parser.parse(options, args, stopAtNonOption);

    Level level;
    if (line.hasOption("very-very-verbose")) {
      level = Level.DEBUG;
    } else if (line.hasOption("very-verbose")) {
      level = Level.INFO;
    } else if (line.hasOption("verbose")) {
      level = Level.WARN;
    } else {
      level = Level.ERROR;
    }
    Logger root = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
    root.setLevel(level);

    if (hasFlagOrCommand(line, "help")) {
      printHelp(usage, options);
      return null;
    }
    if (hasFlagOrCommand(line, "version")) {
      printVersion();
      return null;
    }

    return line;
  }

  /**
   * Parse the command line, handle help and other common options, (May exit!) and return a
   * CommandLine.
   *
   * @param usage the usage string for this command
   * @param options the command-line options for this command
   * @param args the command-line arguments provided
   * @return a new CommandLine object or exit(0)
   * @throws ParseException if the arguments cannot be parsed
   * @throws IOException on issue printing version
   */
  public static CommandLine getCommandLine(String usage, Options options, String[] args)
      throws ParseException, IOException {
    CommandLine line = maybeGetCommandLine(usage, options, args, false);
    if (line == null) {
      System.exit(0);
    }
    return line;
  }

  /**
   * Shared method for dealing with exceptions, printing help, and exiting. Currently prints the
   * error message, stack trace (DEBUG), usage, and then exits.
   *
   * @param exception the exception to handle
   */
  public static void handleException(Exception exception) {
    ExceptionHelper.handleException(exception);
    System.exit(1);
  }

  /**
   * Shared method for dealing with exceptions, printing help, and exiting. Currently prints the
   * error message, stack trace (DEBUG), usage, and then exits.
   *
   * @param usage the usage string for this command; WARN: not used
   * @param options the command-line options for this command; WARN: not used
   * @param exception the exception to handle
   */
  public static void handleException(String usage, Options options, Exception exception) {
    ExceptionHelper.handleException(exception);
    System.exit(1);
  }

  /**
   * Given an input string, return a list of the string split on whitespace, while ignoring any
   * whitespace in single string quotes.
   *
   * @param selects String of select options to split
   * @return List of split strings
   */
  protected static List splitSelects(String selects) {
    List split = new ArrayList<>();
    Matcher m = Pattern.compile("([^\\s]+=.*'[^']+'[^\\s']*|[^\\s']+)").matcher(selects);
    while (m.find()) {
      String s = m.group(1).trim();
      split.add(s);
    }
    return split;
  }

  /**
   * Given a wildcard pattern as string, return an array of files matching that pattern.
   *
   * @param pattern wildcard pattern to match
   * @return array of files
   * @throws IllegalArgumentException on bad pattern
   */
  private static File[] getFilesByPattern(String pattern) throws IllegalArgumentException {
    if (!pattern.contains("*") && !pattern.contains("?")) {
      throw new IllegalArgumentException(wildcardError);
    }
    // Get the parent directory path
    File filePattern = new File(pattern);
    String dir = filePattern.getParent();
    if (dir == null) {
      dir = ".";
    }
    // Make sure this directory exists
    if (!new File(dir).isDirectory()) {
      // Warn user, but continue (empty input checked later)
      logger.error("'{}' is not a valid directory for --inputs pattern", dir);
      return new File[] {};
    }
    FileFilter fileFilter = new WildcardFileFilter(filePattern.getName());
    File[] files = new File(dir).listFiles(fileFilter);
    if (files == null || files.length < 1) {
      // Warn user, but continue (empty input checked later)
      logger.error("No files match pattern: {}", pattern);
      return new File[] {};
    }
    return files;
  }

  /**
   * Given an IOHelper and a command line, check input options and return a list of loaded input
   * ontologies.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param line the command line to use
   * @return the list of input ontologies
   * @throws IllegalArgumentException on bad pattern
   * @throws IOException if the ontology cannot be loaded
   */
  public static List getInputOntologies(IOHelper ioHelper, CommandLine line)
      throws IllegalArgumentException, IOException {
    List inputOntologies = new ArrayList<>();
    // Check for input files
    List inputOntologyPaths = getOptionalValues(line, "input");
    for (String inputOntologyPath : inputOntologyPaths) {
      inputOntologies.add(ioHelper.loadOntology(inputOntologyPath));
    }
    // Check for input IRIs
    List inputOntologyIRIs = getOptionalValues(line, "input-iri");
    for (String inputOntologyIRI : inputOntologyIRIs) {
      inputOntologies.add(ioHelper.loadOntology(IRI.create(inputOntologyIRI)));
    }
    // Check for input patterns (wildcard)
    String pattern = getOptionalValue(line, "inputs");
    if (pattern != null) {
      for (File inputOntologyFile : getFilesByPattern(pattern)) {
        inputOntologies.add(ioHelper.loadOntology(inputOntologyFile));
      }
    }
    return inputOntologies;
  }

  /**
   * Given an IOHelper, a command line, and the path to a catalog file, check input options and
   * return a list of loaded input ontologies with the catalog file.
   *
   * @param ioHelper the IOHelper to load the ontology with
   * @param line the command line to use
   * @param catalogPath the catalog file to use
   * @return the list of input ontologies
   * @throws IOException if the ontology cannot be loaded
   */
  public static List getInputOntologies(
      IOHelper ioHelper, CommandLine line, String catalogPath) throws IOException {
    List inputOntologies = new ArrayList<>();
    // Check for input files
    List inputOntologyPaths = getOptionalValues(line, "input");
    for (String inputOntologyPath : inputOntologyPaths) {
      inputOntologies.add(ioHelper.loadOntology(inputOntologyPath, catalogPath));
    }
    // Check for input IRIs
    List inputOntologyIRIs = getOptionalValues(line, "input-iri");
    for (String inputOntologyIRI : inputOntologyIRIs) {
      inputOntologies.add(ioHelper.loadOntology(IRI.create(inputOntologyIRI), catalogPath));
    }
    // Check for input patterns (wildcard)
    String pattern = getOptionalValue(line, "inputs");
    if (pattern != null) {
      File catalogFile = new File(catalogPath);
      for (File inputOntologyFile : getFilesByPattern(pattern)) {
        inputOntologies.add(ioHelper.loadOntology(inputOntologyFile, catalogFile));
      }
    }
    return inputOntologies;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy