/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.com) * Copyright (C) 2010, Stefan Hepp (stefan@stefant.org). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.config; import com.jopdesign.common.AppInfo; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.misc.AppInfoError; import com.jopdesign.common.misc.MethodNotFoundException; import com.jopdesign.common.processormodel.ProcessorModel.Model; import com.jopdesign.common.type.MemberID; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; /** * Configuration container, based on String-properties. * Option handling and -parsing is done by the {@link OptionGroup} class. * * @author Benedikt Huber <benedikt.huber@gmail.com> * @author Stefan Hepp (stefan@stefant.org) */ public class Config { /* Options which are always present * TODO maybe move the class loading and entry-point related options to AppInfo? */ public static final BooleanOption SHOW_HELP = new BooleanOption("help", "show help", 'h', true); public static final BooleanOption SHOW_VERSION = new BooleanOption("version", "show version number", Option.SHORT_NONE, true); public static final BooleanOption SHOW_CONFIG = new BooleanOption("showconfig", "print current configuration values", Option.SHORT_NONE, true); public static final BooleanOption DEBUG = new BooleanOption("debug", "show debug level messages and write trace messages to info log", 'd', false); public static final BooleanOption QUIET = new BooleanOption("quiet", "only show warnings and errors. Overruled by '-d'", 'q', false); public static final BooleanOption VERBOSE = new BooleanOption("verbose", "use a more detailed log format and show stacktraces. Can be used with '-d' or '-q'", 'v', false); public static final StringOption SHOW_WARN_ONLY = new StringOption("show-warn-only", "Comma-separated list of logger names for which only warn or higher will be printed", true); public static final StringOption SHOW_INFO_ONLY = new StringOption("show-info-only", "Comma-separated list of logger names for which only info or higher will be printed", true); public static final StringOption CLASSPATH = new StringOption("classpath", "classpath of the classes to load", 'c', "java/target/dist/classes"); public static final StringOption MAIN_METHOD_NAME = new StringOption("mm", "method name of the entry method (short name or FQN)", "main"); public static final EnumOption<Model> PROCESSOR_MODEL = new EnumOption<Model>("arch", "The processor model to use", Model.JOP); public static final StringOption HW_OBJECTS = new StringOption("hw-objects", "comma-separated list of hardware (super-) classes and packages", "com.jopdesign.io.HardwareObject"); public static final StringOption LIBRARY_CLASSES = new StringOption("libraries", "comma-separated list of library classes and packages", ""); public static final StringOption IGNORE_CLASSES = new StringOption("ignore", "comma-separated list of classes and packages to ignore", ""); public static final BooleanOption EXCLUDE_LIBRARIES = new BooleanOption("exclude-libs", "do not load library classes", false); public static final BooleanOption EXCLUDE_NATIVES = new BooleanOption("exclude-natives", "do not load native classes", false); public static final StringOption ROOTS = new StringOption("roots", "comma-separated list of additional root classes", ""); public static final IntegerOption CALLSTRING_LENGTH = new IntegerOption("callstring-length", "Length of the callstring", 0); public static final StringOption WRITE_PATH = new StringOption("outdir", "base path for output directories (classdir,reportdir,..)", 'o', "java/target/dist"); public static final StringOption WRITE_CLASSPATH = new StringOption("classdir", "output path for generated class files", "${outdir}/classes"); public static final StringOption REPORTDIR = new StringOption("reportdir", "the directory to write reports into", "${outdir}/report"); public static final StringOption ERROR_LOG_FILE = new StringOption("error-log","the error log file, placed in the report dir","error.log.html"); public static final StringOption INFO_LOG_FILE = new StringOption("info-log","the info log file, placed in the report dir","info.log.html"); public static final StringOption LOAD_SOURCELINES = new StringOption("read-sourcelines", "Read source line infos from file if file exists", "${classpath}/sourcelines.txt"); public static final StringOption WRITE_SOURCELINES = new StringOption("write-sourcelines", "Write source line infos to a file", "${classdir}/sourcelines.txt"); public static final StringOption DUMP_CACHEKEY = new StringOption("dump-cachekey", "Set a filename prefix to dump the checksums of the elements in the cache " + "key into a file",true); public static final Option<?>[] standardOptions = { SHOW_HELP, SHOW_VERSION, SHOW_CONFIG, DEBUG, QUIET, VERBOSE, SHOW_WARN_ONLY, SHOW_INFO_ONLY }; public static final Option<?>[] debugOptions = { DUMP_CACHEKEY }; ///////////////////////////////////////////////////////////////////////////////////////////// // Helper Functions ///////////////////////////////////////////////////////////////////////////////////////////// public static String mergePaths(String[] paths) { if (paths.length == 0) return ""; StringBuilder sb = new StringBuilder(paths[0]); for (int i = 1; i < paths.length; i++) { sb.append(File.pathSeparator); sb.append(paths[i]); } return sb.toString(); } public static String[] splitPaths(String paths) { if (paths.length() == 0) return new String[0]; return paths.split(File.pathSeparator); } /** * @param methodNames comma separated list of method names, either fully qualified with or without descriptor, or * method names of methods in the main class. * @return the set of methods represented by these names * @throws BadConfigurationException if a name is not resolvable */ public static List<MethodInfo> parseMethodList(String methodNames) throws BadConfigurationException { List<String> names = splitStringList(methodNames); List<MethodInfo> methods = new ArrayList<MethodInfo>(names.size()); for (String name : names) { MemberID id = MemberID.parse(name); if (!id.hasClassName()) { ClassInfo main = AppInfo.getSingleton().getMainMethod().getClassInfo(); Set<MethodInfo> m = main.getMethodInfos(id); if (m.isEmpty()) { throw new BadConfigurationException("Cannot find method '"+name+"' in main class "+main); } methods.addAll(m); } else { try { Collection<MethodInfo> infos = AppInfo.getSingleton().getMethodInfos(id); if (infos.isEmpty()) { throw new BadConfigurationException("Cannot find methods for "+name); } methods.addAll(infos); } catch (MethodNotFoundException e) { throw new BadConfigurationException("Cannot find class for "+name, e); } } } return methods; } ///////////////////////////////////////////////////////////////////////////////////////////// // Exceptions ///////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings({"UncheckedExceptionClass"}) public static class BadConfigurationError extends Error { private static final long serialVersionUID = 1L; public BadConfigurationError(String msg) { super(msg); } public BadConfigurationError(String message, Throwable cause) { super(message, cause); } } public static class BadConfigurationException extends Exception { private static final long serialVersionUID = 1L; public BadConfigurationException(String message) { super(message); } public BadConfigurationException(String message, Exception e) { super(message, e); } } private Properties defaultProps, props; private OptionGroup options; private String enableOption; public Config() { this(new Properties()); } public Config(Properties defaultProps) { this.defaultProps = defaultProps; props = new Properties(defaultProps); options = new OptionGroup(this); } /** * Take a comma-separated list of strings and split them into an array, * avoiding empty strings, and trim all entries. * * @param list a comma-separated list, can be null. * @return trimmed entries of list. */ public static List<String> splitStringList(String list) { if (list == null) return new ArrayList<String>(0); String[] parts = list.split(","); List<String> newList = new LinkedList<String>(); for (String part : parts) { part = part.trim(); if ("".equals(part)) { continue; } newList.add(part); } return newList; } /** * Check whether the given file is a directory, possibly creating it if * non existing * * @param outDir the path to the directory * @param createIfNonExist whether the directory should be created, if it doesn't exist yet * @throws BadConfigurationException if the file is not a directory or does not exist and is not created. */ public static void checkDir(File outDir, boolean createIfNonExist) throws BadConfigurationException { if (outDir.exists()) { if (!outDir.isDirectory()) { throw new BadConfigurationException("Not a directory: " + outDir); } } else if (createIfNonExist) { if (!outDir.mkdirs()) { throw new BadConfigurationException("Directory could not be created: " + outDir); } } else { throw new BadConfigurationException("Directory does not exist: " + outDir); } } /** * @return the directory configured by {@link Config#WRITE_PATH} */ public File getOutDir() { return new File(getOption(Config.WRITE_PATH)); } /** * Get a subdirectory under the write path configured by {@link Config#WRITE_PATH} and create it * if it does not exist. * * @param sub name of a subdirectory * @return a file representing the directory. */ public File getOutDir(String sub) { File outDir = getOutDir(); File subDir = new File(outDir, sub); if (!subDir.exists()) { if (!subDir.mkdirs()) { throw new AppInfoError("Failed to create subdirectory "+sub+" in outdir"); } } return subDir; } public File getOutFile(String file) { return new File(getOutDir(), file); } public File getOutDir(OptionGroup group, StringOption option) throws BadConfigurationException { File outDir = new File(group.getOption(option)); checkDir(outDir, true); return outDir; } public File getOutDir(StringOption option) throws BadConfigurationException { return getOutDir(options, option); } public OptionGroup getOptions() { return options; } public OptionGroup getDebugGroup() { return getOptions().getGroup("debug"); } public Properties getProperties() { return props; } /** * Load a properties configuration file and append its content to the current configuration. * Existing keys are replaced. * * @param propStream an open InputStream serving the properties * @throws IOException if loading fails. */ public void addProperties(InputStream propStream) throws IOException { Properties p = new Properties(); p.load(propStream); props.putAll(p); } /** * Load a properties configuration file and append its content to the current configuration. * Existing keys are replaced. * * @param propStream an open InputStream serving the properties * @param prefix a prefix to append to all property keys in the stream before adding them to the configuration. * @throws IOException if loading fails. */ public void addProperties(InputStream propStream, String prefix) throws IOException { Properties p = new Properties(); p.load(propStream); String pfx = prefix == null || "".equals(prefix) ? "" : prefix + "."; for (Map.Entry<Object, Object> e : p.entrySet()) { props.put(pfx + e.getKey(), e.getValue()); } } /** * Get the default value used to indent the descriptions or values of options. * * @return the default indent for help texts. */ public int getDefaultIndent() { int i = 1; for (Option o : options.availableOptions()) { i = Math.max(i, o.getKey().length()); } return Math.max(8, Math.min(i, 25)); } /** * Parse configuration options. * * @param args arguments to parse * @return string-arguments after the last known argument. * @throws BadConfigurationException if arguments or current properties cannot be parsed. * @see OptionGroup#consumeOptions(String[]) */ public String[] parseArguments(String[] args) throws BadConfigurationException { return options.consumeOptions(args); } /** * Check all options for correctness (missing required options, if options can be parsed, .. ). * * @throws BadConfigurationException if an option is missing or cannot be parsed. */ public void checkOptions() throws BadConfigurationException { options.checkOptions(); } public void checkPresent(Option<?> option) throws BadConfigurationException { if (getOption(option) == null) { throw new BadConfigurationException("Missing option: " + option); } } /** * Set a new set of default values, replaces the old default values. * * @param defaultProps the new default values. */ public void setDefaults(Properties defaultProps) { this.defaultProps = defaultProps; Properties oldProps = props; props = new Properties(defaultProps); props.putAll(oldProps); } /** * Add a set of properties to the default properties. * * @param defaults a set of default properties. * @param override if true, override existing default props */ public void addDefaults(Properties defaults, boolean override) { //noinspection unchecked if (override) { defaultProps.putAll(defaults); } else { for (Object key : defaults.entrySet()) { if (!defaultProps.containsKey(key)) { defaultProps.put(key, defaults.get(key)); } } } } /** * Clear all set properties, but not the default values. */ public void clearValues() { props.clear(); } /** * Set a new value for a key. * * @param key the key to of the value to set. * @param value the new value to set, or null to unset it * @param setDefault if true, set the default value instead of the value. * @return the old value or old default value. */ public String setProperty(String key, String value, boolean setDefault) { Object val; if (setDefault) { if (value != null) { val = defaultProps.setProperty(key, value); } else { val = defaultProps.remove(key); } } else { if (value != null) { val = props.setProperty(key, value); } else { val = props.remove(key); } } return val != null ? val.toString() : null; } public String setProperty(String key, String value) { return setProperty(key, value, false); } public String setDefaultProperty(String key, String value) { return setProperty(key, value, true); } /** * Check if a key is set (ignoring default options). * * @param key the key of the property to check. * @return true if set. * @see #hasValue(String) */ public boolean isSet(String key) { return props.containsKey(key); } /** * Check if the value of an option has been set (ignoring default options). * * @param option the option to check. * @return true if set. * @see #hasValue(String) */ public boolean isSet(Option<?> option) { return isSet(options.getConfigKey(option)); } /** * Check if a key is set or has a default value. * * @param key the key of the property to check. * @return true if it has a value or default not equal to null. * @see #isSet(String) */ public boolean hasValue(String key) { return props.getProperty(key) != null; } /** * Check if an option is set or has a default value. * * @param option the option of the property to check. * @return true if it has a value or default not equal to null. * @see #isSet(String) * @see OptionGroup#hasValue(Option) */ public boolean hasValue(Option<?> option) { return options.hasValue(option); } public String getValue(String key) { return props.getProperty(key); } public String getValue(String key, String defaultVal) { return props.getProperty(key, defaultVal); } public String getDefaultValue(String key) { return defaultProps.getProperty(key); } /** * This is a shortcut to add an option to the main option group. * * @param option the option to add. * @see OptionGroup#addOption(Option) */ public void addOption(Option<?> option) { options.addOption(option); } public void addOption(Option<?> option, boolean available) { options.addOption(option, available); } /** * This is a shortcut to add a list of options to the main option group. * * @param options the options to add. * @see OptionGroup#addOptions(Option[]) */ public void addOptions(Option<?>[] options) { this.options.addOptions(options); } public void addOptions(Option<?>[] options, boolean available) { this.options.addOptions(options, available); } /** * This is a shortcut to add a list of options to the main option group. * * @param options the options to add. * @see OptionGroup#addOptions(Option[][]) */ public void addOptions(Option<?>[][] options) { this.options.addOptions(options); } /** * Check if the given option has been added (e.g. by using {@link #addOption}). * * @param option the option to check * @return true if the option has been added to the OptionGroup. */ public boolean hasOption(Option<?> option) { return this.options.containsOption(option); } /** * This is a shortcut to get an option from the main option group. * * @param option the option to read. * @return the value of the option * @throws Config.BadConfigurationError if the format of the option is invalid if required and not set. * @see OptionGroup#getOption(Option) */ public <T> T getOption(Option<T> option) throws BadConfigurationError { return options.getOption(option); } /** * This is a shortcut to get an option from the main option group. * * @param option the option to read. * @param defaultVal the default value to use if no other value is found. * @return the value of the option * @throws IllegalArgumentException if the format of the option is invalid * @see OptionGroup#getOption(Option, Object) */ public <T> T getOption(Option<T> option, T defaultVal) throws IllegalArgumentException { return options.getOption(option, defaultVal); } public <T> T getDefaultValue(Option<T> option) { return options.getDefaultValue(option); } public <T> void setOption(Option<T> option, T value) { options.setOption(option, value); } public <T> void setDefaultValue(Option<T> option, T defaultValue) { options.setDefaultValue(option, defaultValue); } /** * Check if an option is enabled. * @see Option#isEnabled(OptionGroup) * @param fullKey the key of the option, can specify an option in a subgroup. * @return true if the option is enabled, i.e. has a value different from null, 0 or false, * depending on the option type. */ public boolean isEnabled(String fullKey) { Option option = findOption(fullKey); OptionGroup group = findOptionGroup(fullKey); return option.isEnabled(group); } public Option findOption(String fullKey) { return options.findOption(fullKey); } public OptionGroup findOptionGroup(String fullKey) { return options.findOptionGroup(fullKey); } /** * This is a shortcut to get an option from the main option group. * * @param option the option to read. * @return the value of the option or null if not set, even if required. * @throws IllegalArgumentException if the format of the option is invalid * @see OptionGroup#tryGetOption(Option) */ public <T> T tryGetOption(Option<T> option) throws IllegalArgumentException { return options.tryGetOption(option); } /** * Dump configuration of all user-set properties for debugging purposes. * To print a list of all options with their values, * use {@link #printConfiguration(int)}. * * @param indent indent used for keys * @return a dump of all options with their respective values. * @see #printConfiguration(int) */ public String dumpConfiguration(int indent) { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream p = new PrintStream(os); for (Map.Entry<Object, Object> e : props.entrySet()) { printOption(p, indent, e.getKey().toString(), e.getValue()); } return os.toString(); } public void printConfiguration(int indent) { Set<String> keys = new HashSet<String>(); keys.addAll(options.printOptions(System.out, indent)); System.out.println(); System.out.println("Other configuration values:"); for (Map.Entry<Object, Object> e : props.entrySet()) { if (keys.contains(e.getKey().toString())) { continue; } printOption(System.out, indent, e.getKey().toString(), e.getValue()); } System.out.println(); } /** * Set the key of the currently processed tool enable/disable option. Options added after this has * been set will only be required if this option is enabled. * * @param enableOption name of the option which must be enabled to make added options required. */ public void setEnableOption(String enableOption) { this.enableOption = enableOption; } public void unsetEnableOption() { enableOption = null; } /** * This returns the name of the currently set option key used to check if options should be required. * @see #setEnableOption(String) * @return the currently set option key or null if not set. */ public String getEnableOption() { return enableOption; } protected static void printOption(PrintStream p, int indent, String key, Object value) { p.println(String.format("%4s%-" + (indent + 1) + "s ==> %s", "", key, value == null ? "<not set>" : value)); } }