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

fr.univnantes.termsuite.tools.CommandLineClient Maven / Gradle / Ivy

package fr.univnantes.termsuite.tools;

import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.OutputStreamAppender;
import fr.univnantes.termsuite.api.IndexedCorpusIO;
import fr.univnantes.termsuite.api.TermSuite;
import fr.univnantes.termsuite.model.IndexedCorpus;
import fr.univnantes.termsuite.model.Lang;
import fr.univnantes.termsuite.model.Property;
import fr.univnantes.termsuite.model.RelationProperty;
import fr.univnantes.termsuite.model.TermProperty;
import fr.univnantes.termsuite.tools.opt.CliOption;
import fr.univnantes.termsuite.tools.opt.ClientHelper;
import fr.univnantes.termsuite.tools.opt.OptType;
import fr.univnantes.termsuite.tools.opt.TermSuiteCliOption;

public abstract class CommandLineClient {
	private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineClient.class);
	
	protected ClientHelper clientHelper = new ClientHelper(this);
	
	protected String description;
	public abstract void configureOpts();
	protected abstract void run() throws Exception;
	protected Options options = new Options();
	protected CommandLine line = null;

	protected List> atLeastOneOfBags= new ArrayList<>();
	protected List> atMostOneOfBags= new ArrayList<>();
	protected List> exactlyOneOfBags= new ArrayList<>();
	protected Set declaredOptions = new HashSet<>();
	protected Set facultativeOptions = new HashSet<>();
	protected Set mandatoryOptions = new HashSet<>();
	protected Map> conditionalOptions = new HashMap<>();
	protected Map> conditionalAbsentOptions = new HashMap<>();

	public CommandLineClient(String description) {
		super();
		this.description = description;
	}
	public void declareAtMostOneOf(CliOption first, CliOption second, CliOption... others) {
		atMostOneOfBags.add(toBag(first, second, others));
	}

	public void declareAtLeastOneOf(CliOption first, CliOption second, CliOption... others) {
		atLeastOneOfBags.add(toBag(first, second, others));
	}
	
	public void declareExactlyOneOf(CliOption first, CliOption second, CliOption... others) {
		exactlyOneOfBags.add(toBag(first, second, others));
	}
	
	private Set toBag(CliOption first, CliOption second, CliOption... others) {
		Set bag = new HashSet<>();
		bag.add(first);
		if(!declaredOptions.contains(first))
			declareFacultative(first);
		bag.add(second);
		if(!declaredOptions.contains(second))
			declareFacultative(second);
		for(CliOption opt:others) {
			bag.add(opt);
			if(!declaredOptions.contains(opt))
				declareFacultative(opt);
		}
		return bag;
	}
	
	public void declareMandatory(CliOption option) {
		Option opt = addDeclaredOption(option);
		opt.setRequired(true);
		mandatoryOptions.add(option);
	}
	
	/**
	 * 
	 * Declare some option mandatory only when another 
	 * facultative (optional) option is present.
	 * 
	 * @param conditionalOpt
	 * @param mandatoryOptsIfPresent
	 */
	public void declareConditional(CliOption conditionalOpt, CliOption... mandatoryOptsIfPresent) {
		Preconditions.checkArgument(declaredOptions.contains(conditionalOpt), "%s must be declared as optional", conditionalOpt);
		Set set = Sets.newHashSet(mandatoryOptsIfPresent);
		for(CliOption opt:set)
			if(!declaredOptions.contains(opt))
				declareFacultative(opt);
		if(conditionalOptions.containsKey(conditionalOpt))
			conditionalOptions.get(conditionalOpt).addAll(set);
		else
			conditionalOptions.put(conditionalOpt, set);
	}
	
	/**
	 * 
	 * Declares a list of options that cannot appear when
	 * a conditional option is set.
	 * 
	 * @param conditionalOpt
	 * @param mustBeAbsentOnCondition
	 */
	public void declareCannotAppearWhenCondition(CliOption conditionalOpt, CliOption... mustBeAbsentOnCondition) {
		Preconditions.checkArgument(declaredOptions.contains(conditionalOpt), "%s must be declared as optional", conditionalOpt);
		Set set = Sets.newHashSet(mustBeAbsentOnCondition);
		if(conditionalAbsentOptions.containsKey(conditionalOpt))
			conditionalAbsentOptions.get(conditionalOpt).addAll(set);
		else
			conditionalAbsentOptions.put(conditionalOpt, set);

	}

	
	public void declareFacultative(CliOption option) {
		Option opt = addDeclaredOption(option);
		opt.setRequired(false);
		facultativeOptions.add(option);
	}
	
	private Option addDeclaredOption(CliOption option) {
		Preconditions.checkArgument(
				!declaredOptions.contains(option),
				"Option %s already declared.", 
				option);
		declaredOptions.add(option);
		Option opt = new Option(
				option.getOptShortName(), 
				option.getOptName(), 
				option.hasArg(), 
				option.getDescription());
		if(option.getArgType() == OptType.T_TERM_LIST)
			opt.setArgs(Option.UNLIMITED_VALUES);
		options.addOption(opt);
		return opt;
	}
	
	public Optional getOpt(CliOption opt) {
		if(isSet(opt))
			return Optional.of(line.getOptionValue(opt.getOptName()));
		else
			return Optional.empty();
	}

	public boolean hasOpt(CliOption opt) {
		Preconditions.checkState(
				line != null, "Command line not parsed");
		return line.hasOption(opt.getOptName());
	}
	
	public void checkClientState(CliOption opt) {
		Preconditions.checkState(
				line != null, "Command line not parsed");
		Preconditions.checkArgument(
				!declaredOptions.contains(opt),
				"Option %s not declared", 
				opt);
	}
	
	public Path asPath(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		Path path = Paths.get(getOpt(opt).get().trim());
		if(path.getParent() != null)
			path.getParent().toFile().mkdirs();
		return path;
	}

	protected IndexedCorpus asIndexedCorpus(CliOption path) {
		return IndexedCorpusIO.fromJson(asPath(path));
	}

	public Path asDir(CliOption opt) {
		Path path = asPath(opt);
		if(path.toFile().exists())
			Preconditions.checkArgument(path.toFile().isDirectory(), "Not a directory: ", path);
		else
			path.toFile().mkdirs();
		return path;
	}
	
	public boolean asBoolean(CliOption opt) {
		Preconditions.checkState(!opt.hasArg());
		return hasOpt(opt);
	}

	public int asInt(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		return Integer.parseInt(getOpt(opt).get().trim());
	}

	public List getList(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		return Splitter.on(",")
				.splitToList(getOpt(opt).get()
						.trim()
						.replaceAll("^\"", "")
						.replaceAll("\"$", "")
						);
	}
	
	public boolean isSet(CliOption opt) {
		return line.hasOption(opt.getOptName());
	}

	public long asLong(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		return Long.parseLong(getOpt(opt).get().trim());
	}
	
	public List asList(CliOption opt) {
		return Splitter.on(",").splitToList(asString(opt));
	}
	
	public String[] asArray(CliOption opt) {
		List list = asList(opt);
		return list.toArray(new String[list.size()]);
	}

	public String asString(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		return getOpt(opt).get().trim();
	}
	public List asTermString(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		Preconditions.checkArgument(isSet(opt));
		String[] optionValues = line.getOptionValues(opt.getOptName());
		String paramStr = Joiner.on(" ").join(optionValues);
		List termStrings = new ArrayList<>();
		for(String termStr:paramStr.split(","))
			termStrings.add(termStr.trim().replaceAll("\\s+", " "));
		return termStrings;
	}

	public Lang getLang() {
		return Lang.forName(asString(TermSuiteCliOption.LANGUAGE));
	}

	public double asDouble(CliOption opt) {
		Preconditions.checkState(opt.hasArg());
		return Double.parseDouble(getOpt(opt).get().trim());
	}
	
	public void launch(String... args) {
		LogbackUtils.reset();
		configureGeneralOpts();
		configureOpts();
		try {
			this.line = new PosixParser().parse(options, args);
			applyLogConfig();
			if(isSet(TermSuiteCliOption.HELP)) {
				doHelp(System.out);
				return;
			}
			CliUtil.logCommandLineOptions(LOGGER, line);
			checkAtLeastOneOf();
			checkMandatoryOpts();
			checkAtMostOneOf();
			checkExactlyOneOf();
			checkConditionals();
			checkConditionalAbsent();
			run();
			if(clientHelper.getHistory().isPresent()) 
				LOGGER.info("Watched terms: {}", clientHelper.getHistory());
		} catch (ParseException e) {
			LOGGER.error("Could not parse arguments {}", e.getMessage());
			throw new TermSuiteCliException("Could not parse arguments. Error: " + e.getMessage(), e);
		} catch (Exception e) {
			LOGGER.error("An unexpected error occurred: " + e.getMessage(), e);
			throw new TermSuiteCliException("An unexpected error occurred: " + e.getMessage(), e);
		}
	}
	
	private void applyLogConfig() {
		boolean loggingActivated = isSet(TermSuiteCliOption.LOG_INFO) 
				|| isSet(TermSuiteCliOption.LOG_DEBUG) 
				|| isSet(TermSuiteCliOption.LOG_TO_FILE);
		
		if(loggingActivated) {
			OutputStreamAppender appender = 
					isSet(TermSuiteCliOption.LOG_TO_FILE) ?
							LogbackUtils.createFileAppender(asPath(TermSuiteCliOption.LOG_TO_FILE)) :
								LogbackUtils.createConsoleAppender();
			LogbackUtils.activateLogging(appender);
			
			Level level = Level.WARN;
			if(isSet(TermSuiteCliOption.LOG_INFO))
				level = Level.INFO;
			else if(isSet(TermSuiteCliOption.LOG_DEBUG))
				level = Level.DEBUG;
			else if(isSet(TermSuiteCliOption.LOG_TO_FILE))
				level = Level.INFO;
			LogbackUtils.setGlobalLogLevel(level);
		}
	}
	private void configureGeneralOpts() {
		declareFacultative(TermSuiteCliOption.HELP);
		declareFacultative(TermSuiteCliOption.LOG_TO_FILE);
		declareFacultative(TermSuiteCliOption.LOG_DEBUG);
		declareFacultative(TermSuiteCliOption.LOG_INFO);
		declareAtMostOneOf(TermSuiteCliOption.LOG_INFO, TermSuiteCliOption.LOG_DEBUG);
	}
	
	protected void runClient(String[] args) {
		try {
			launch(args);
		} catch(TermSuiteCliException e) {
			System.out.flush();
			System.err.flush();
			System.err.format("ERROR: %s%n---%nRun again with option --%s for more information", e.getMessage());
			System.exit(1);
		}
	}

	public void doHelp(PrintStream out) {
		out.format("Usage:%n\tjava [-Xms256m -Xmx8g] -cp termsuite-core-%s.jar %s OPTIONS %n",
				TermSuite.currentVersion(),
				this.getClass().getCanonicalName());
		out.format("%nDescription:%n\t%s%n", description);
		out.format("%nMandatory options:%n");
		for(CliOption opt:mandatoryOptions) {
			out.format("\t%-3s --%-35s %s%n", 
					opt.getOptShortName() == null ? "" : ("-"+opt.getOptShortName()+""),
					opt.getOptName() + " " + (opt.hasArg() ? opt.getArgType().getStr() : ""),
					opt.getDescription()
							
				);
		}
		out.format("%nOther options:%n");
		List otherOptions = getSortedDeclaredOptions();
		otherOptions.removeAll(mandatoryOptions);
		for(CliOption opt:otherOptions) {
			out.format("\t%-3s --%-35s %s%n", 
					opt.getOptShortName() == null ? "" : ("-"+opt.getOptShortName()+""),
					opt.getOptName() + " " + (opt.hasArg() ? opt.getArgType().getStr() : ""),
					opt.getDescription()
							
				);
		}

	}
	
	public List getSortedDeclaredOptions() {
		ArrayList sorted = Lists.newArrayList(declaredOptions);
		sorted.sort(new Comparator() {
			@Override
			public int compare(CliOption o1, CliOption o2) {
				if(mandatoryOptions.contains(o1) && !mandatoryOptions.contains(o2))
					return -1;
				else if(mandatoryOptions.contains(o2) && !mandatoryOptions.contains(o1))
					return 1;
				else
					return o1.getOptName().compareTo(o2.getOptName());
			}
		});
		return sorted;
	}
	

	private void checkMandatoryOpts() {
		for(CliOption opt:mandatoryOptions) {
			if(!isSet(opt)) {
				String shortOptString = opt.getOptShortName() != null ? " [-"+opt.getOptShortName()+"]" : "";
				CliUtil.throwException("Option --%s is mandatory", 
					opt.getOptName() + shortOptString);
			}
		}
	}
	
	private void checkConditionals() {
		for(CliOption condition:conditionalOptions.keySet()) {
			if(!isSet(condition)) {
				for(CliOption dependingOpt:conditionalOptions.get(condition)) {
					if(isSet(dependingOpt)) {
						CliUtil.throwException("Option --%s can be set only when option --%s is present", 
								dependingOpt.getOptName(), 
								condition.getOptName());
					}
				}
			}
		}
	}

	private void checkConditionalAbsent() {
		for(CliOption condition:conditionalAbsentOptions.keySet()) {
			if(isSet(condition)) {
				for(CliOption dependingOpt:conditionalAbsentOptions.get(condition)) {
					if(isSet(dependingOpt)) {
						CliUtil.throwException("Option --%s cannot be set when option --%s is present", 
								dependingOpt.getOptName(), 
								condition.getOptName());
					}
				}
			}
		}
	}
	

	public void checkExactlyOneOf() {
		for(Set bag:exactlyOneOfBags) {
			Set foundOpts = new HashSet<>();
			for(CliOption opt:bag) 
				if(isSet(opt))
					foundOpts.add(opt);
			if(foundOpts.size() != 1)
				CliUtil.throwExactlyOne(bag);
		}
	}
	
	public void checkAtMostOneOf() {
		for(Set bag:atMostOneOfBags) {
			Set foundOpts = new HashSet<>();
			for(CliOption opt:bag) 
				if(isSet(opt))
					foundOpts.add(opt);
			if(foundOpts.size() > 1)
				CliUtil.throwAtMost(bag);
		}
	}
	
	public void checkAtLeastOneOf() {
		for(Set bag:atLeastOneOfBags) {
			boolean found = false;
			for(CliOption opt:bag) 
				found |= isSet(opt);
			if(!found)
				CliUtil.throwAtLeast(bag);
		}
	}
	
	public Property asProperty(CliOption opt) {
		String propertyName = asString(opt);
		return asProperty(propertyName);
	}
	
	public Property asProperty(String propertyName) {
		try {
			return TermProperty.forName(propertyName);
		} catch(IllegalArgumentException e) {
			try {
				return RelationProperty.forName(propertyName);
			} catch(IllegalArgumentException e2) {
				throw new TermSuiteCliException(String.format("No term or relation property for name \"%s\".", propertyName));
			}
		}
	}

	
	public TermProperty asTermProperty(String pName) {
		try {
			return TermProperty.forName(pName);
		} catch(IllegalArgumentException e) {
			throw new TermSuiteCliException(e);
		}
	}
	
	public TermProperty asTermProperty(CliOption opt) {
		return asTermProperty(asString(opt));
	}
	
	public RelationProperty asRelationProperty(CliOption opt) {
		return asRelationProperty(asString(opt));
	}
	private RelationProperty asRelationProperty(String pName) {
		try {
			return RelationProperty.forName(pName);
		} catch(IllegalArgumentException e) {
			throw new TermSuiteCliException(e);
		}
	}

	public Class asClass(CliOption opt) {
		try {
			return (Class)Class.forName(asString(opt));
		} catch (ClassNotFoundException e) {
			CliUtil.throwException("No suitable class found for option %s", opt);
			return null;
		}
	}
	
	
	public List> getAtLeastOneOfBags() {
		return atLeastOneOfBags;
	}
	public List> getAtMostOneOfBags() {
		return atMostOneOfBags;
	}
	public List> getExactlyOneOfBags() {
		return exactlyOneOfBags;
	}
	public Set getMandatoryOptions() {
		return mandatoryOptions;
	}
	public Map> getConditionalOptions() {
		return conditionalOptions;
	}
	public Map> getConditionalAbsentOptions() {
		return conditionalAbsentOptions;
	}
	public String getDescription() {
		return description;
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy