package org.esa.beam.smos.ee2netcdf;
import com.bc.ceres.binding.Property;
import com.bc.ceres.binding.PropertyContainer;
import com.bc.ceres.binding.ValidationException;
import org.apache.commons.cli.*;
import org.esa.beam.framework.gpf.annotations.Parameter;
import org.esa.beam.framework.gpf.annotations.ParameterDescriptorFactory;
import org.esa.beam.smos.gui.BindingConstants;
import org.esa.beam.util.StringUtils;
import org.esa.beam.util.logging.BeamLogManager;
import java.io.File;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.*;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Command line tool for converting SMOS products from Earth Explorer Format to netCDF.
*
* @author Ralf Quast
*/
public class GPToNetCDFExporterTool {
private static final String TOOL_NAME = "ee2netcdf";
private static final String TOOL_VERSION = "1.0";
private static final int ERROR = 1;
private static final int USAGE_ERROR = 2;
private static final int EXECUTION_ERROR = 3;
private static final Map<String, String> PARAMETER_NAMES = new HashMap<>();
static {
Locale.setDefault(Locale.ENGLISH);
PARAMETER_NAMES.put(BindingConstants.CONTACT, "contact");
PARAMETER_NAMES.put(BindingConstants.INSTITUTION, "institution");
PARAMETER_NAMES.put(BindingConstants.OVERWRITE_TARGET, "overwrite-target");
PARAMETER_NAMES.put(BindingConstants.REGION, "region");
PARAMETER_NAMES.put(BindingConstants.TARGET_DIRECTORY, "target-directory");
PARAMETER_NAMES.put(BindingConstants.VARIABLES, "variables");
PARAMETER_NAMES.put(BindingConstants.COMPRESSION_LEVEL, "compression-level");
}
private static final Level[] LOG_LEVELS = new Level[]{
Level.ALL,
Level.INFO,
Level.CONFIG,
Level.WARNING,
Level.SEVERE,
Level.OFF
};
private static final String LOG_LEVEL_OPTION_NAME = "log-level";
private static final String ERROR_OPTION_NAME = "error";
private static final String LOG_LEVEL_DESCRIPTION = "Set the logging level to <level> where <level> must be in "
+ Arrays.toString(LOG_LEVELS).replace("[", "{").replaceAll("]",
"}")
+ ". The default logging level is '"
+ Level.INFO.toString()
+ "'.";
private final Options options = new Options();
private Logger logger;
private Level logLevel = Level.INFO;
private boolean produceErrorMessages;
public static void main(String[] args) {
new GPToNetCDFExporterTool().run(args);
}
public GPToNetCDFExporterTool() {
defineOptions();
}
private void run(String[] arguments) {
try {
execute(arguments);
} catch (ToolException e) {
exit(e, e.getExitCode());
} catch (Throwable e) {
exit(e, ERROR);
}
}
private void execute(String[] arguments) throws ToolException {
final CommandLine commandLine;
try {
commandLine = parseCommandLine(arguments);
if (commandLine.hasOption("help")) {
printHelp();
return;
}
if (commandLine.hasOption("version")) {
printVersion();
return;
}
if (commandLine.getArgs().length == 0 && !commandLine.hasOption("source-product-paths")) {
printHelp();
return;
}
if (commandLine.hasOption(ERROR_OPTION_NAME)) {
produceErrorMessages = true;
}
if (commandLine.hasOption(LOG_LEVEL_OPTION_NAME)) {
final String optionValue = commandLine.getOptionValue(LOG_LEVEL_OPTION_NAME);
logLevel = Level.parse(optionValue);
}
} catch (ParseException | IllegalArgumentException e) {
throw new ToolException(e, USAGE_ERROR);
} finally {
configureLogger();
}
final ExportParameter exportParameter = new ExportParameter();
setExportParameters(commandLine, exportParameter);
final GPToNetCDFExporter exporter = new GPToNetCDFExporter(exportParameter);
try {
exporter.initialize();
} catch (Exception e) {
final File targetDirectory = exportParameter.getTargetDirectory();
throw new ToolException(MessageFormat.format("The target directory ''{0}'' could not be created.", targetDirectory),
e, EXECUTION_ERROR);
}
for (final String path : commandLine.getArgs()) {
final File file = new File(path);
try {
exporter.exportFile(file, getLogger());
} catch (Exception e) {
throw new ToolException(
MessageFormat.format("An error has occurred while trying to convert file ''{0}''.", path), e,
EXECUTION_ERROR);
}
}
if (commandLine.hasOption("source-product-paths")) {
final String sourceProductPathsCSV = commandLine.getOptionValue("source-product-paths");
final String[] sourceProductPaths = StringUtils.split(sourceProductPathsCSV, new char[]{','}, true);
final TreeSet<File> inputFileSet = ExporterUtils.createInputFileSet(sourceProductPaths);
for (File inputFile : inputFileSet) {
if (inputFile.isDirectory()) {
continue;
}
exporter.exportFile(inputFile, getLogger());
}
}
}
private void setExportParameters(CommandLine commandLine, ExportParameter exportParameter) throws ToolException {
final ParameterDescriptorFactory descriptorFactory = new ParameterDescriptorFactory();
final PropertyContainer container = PropertyContainer.createObjectBacked(exportParameter, descriptorFactory);
container.setDefaultValues();
container.setValue(BindingConstants.ROI_TYPE, BindingConstants.ROI_TYPE_GEOMETRY);
for (final String parameterName : PARAMETER_NAMES.keySet()) {
final String optionName = getOptionName(parameterName);
if (commandLine.hasOption(optionName)) {
final String optionValue = commandLine.getOptionValue(optionName);
final Property parameter = container.getProperty(parameterName);
if (optionValue == null) {
if (parameter.getType().isAssignableFrom(boolean.class)) {
container.setValue(parameterName, true);
continue;
}
}
try {
parameter.setValueFromText(optionValue);
} catch (ValidationException e) {
throw new ToolException(
MessageFormat.format("Missing or invalid value for option ''{0}''.", optionName), e,
USAGE_ERROR);
}
}
}
}
private void exit(Throwable t, int exitCode) {
if (produceErrorMessages) {
System.err.println(t.getMessage());
t.printStackTrace(System.err);
}
if (getLogger().isLoggable(Level.SEVERE)) {
getLogger().log(Level.SEVERE, t.getMessage());
if (getLogger().isLoggable(Level.FINE)) {
for (StackTraceElement e : t.getStackTrace()) {
getLogger().log(Level.FINE, e.toString());
}
}
}
System.exit(exitCode);
}
private Logger getLogger() {
if (logger == null) {
logger = BeamLogManager.getSystemLogger();
}
return logger;
}
private void configureLogger() {
final Logger logger = getLogger();
logger.setLevel(logLevel);
final ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(logLevel);
logger.addHandler(consoleHandler);
}
private void printVersion() {
System.out.println(TOOL_NAME + " version " + TOOL_VERSION);
}
private void printHelp() {
HelpFormatter helpFormatter = new HelpFormatter();
helpFormatter.setNewLine("\n");
helpFormatter.setWidth(80);
helpFormatter.printHelp(getSyntax(), "\nOptions:", options, "", false);
}
private String getSyntax() {
return TOOL_NAME + " [options] file ...";
}
private CommandLine parseCommandLine(String[] arguments) throws ParseException {
return new PosixParser().parse(options, arguments);
}
private void defineOptions() {
options.addOption("e", "errors", false, "Produce execution error messages.");
options.addOption("h", "help", false, "Display help information.");
options.addOption("v", "version", false, "Display version information.");
options.addOption(createOption("l", LOG_LEVEL_OPTION_NAME, Level.class, LOG_LEVEL_DESCRIPTION));
OptionBuilder.withLongOpt("source-product-paths");
OptionBuilder.hasArg(true);
OptionBuilder.withArgName(String.class.getSimpleName().toLowerCase());
OptionBuilder.withDescription("Comma-separated list of file paths specifying the source products.\n" +
"Each path may contain the wildcards '**' (matches recursively any directory),\n" +
"'*' (matches any character sequence in path names) and\n" +
"'?' (matches any single character).");
options.addOption(OptionBuilder.create());
final Set<String> parameterNames = PARAMETER_NAMES.keySet();
final Field[] fields = ExportParameter.class.getDeclaredFields();
for (final Field field : fields) {
final Parameter parameter = field.getAnnotation(Parameter.class);
if (parameter != null) {
final String alias = parameter.alias();
if (parameterNames.contains(alias)) {
final String optionName = getOptionName(alias);
OptionBuilder.withLongOpt(optionName);
final Class<?> type = getType(field);
OptionBuilder.withType(type);
final String argName = type.getSimpleName().toLowerCase();
final boolean noArg = type.isAssignableFrom(boolean.class);
if (noArg) {
OptionBuilder.hasArg(false);
} else {
OptionBuilder.hasArg(true);
OptionBuilder.withArgName(argName);
}
final String description = parameter.description();
final StringBuilder descriptionBuilder = new StringBuilder(description);
if (!description.isEmpty() && !description.endsWith(".")) {
descriptionBuilder.append(".");
}
if (!noArg) {
final String[] valueSet = parameter.valueSet();
if (valueSet.length != 0) {
descriptionBuilder
.append(" The argument <")
.append(argName)
.append("> must be in ")
.append(Arrays.toString(valueSet).replace("[", "{").replace("]", "}"))
.append(".");
} else {
final String interval = parameter.interval();
if (!interval.isEmpty()) {
descriptionBuilder
.append(" The argument <")
.append(argName)
.append("> must be in the interval ")
.append(interval)
.append(".");
}
}
final String defaultValue = parameter.defaultValue();
if (!defaultValue.isEmpty()) {
descriptionBuilder
.append(" The default value is '")
.append(defaultValue)
.append("'.");
}
}
OptionBuilder.withDescription(descriptionBuilder.toString());
final Option option = OptionBuilder.create();
final boolean required = parameter.notNull() || parameter.notEmpty();
option.setRequired(required);
options.addOption(option);
}
}
}
}
private static Option createOption(String opt, String optionName, Class<?> argType, String description) {
final Option option = new Option(opt, optionName, true, description);
option.setType(argType);
option.setArgName(argType.getSimpleName().toLowerCase());
return option;
}
private static String getOptionName(String alias) {
return PARAMETER_NAMES.get(alias);
}
private static Class<?> getType(Field field) {
final Class<?> fieldType = field.getType();
if (fieldType.isPrimitive() || fieldType.equals(File.class)) {
return fieldType;
}
return String.class;
}
private static final class ToolException extends Exception {
private final int exitCode;
private ToolException(Throwable cause, int exitCode) {
super(cause);
this.exitCode = exitCode;
}
private ToolException(String message, Throwable cause, int exitCode) {
super(message, cause);
this.exitCode = exitCode;
}
public int getExitCode() {
return exitCode;
}
}
}