/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.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; import com.jopdesign.common.bcel.CustomAttribute; import com.jopdesign.common.config.BooleanOption; import com.jopdesign.common.config.Config; import com.jopdesign.common.config.Config.BadConfigurationError; import com.jopdesign.common.config.Config.BadConfigurationException; import com.jopdesign.common.config.Option; import com.jopdesign.common.config.OptionGroup; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.ClassInfoNotFoundException; import com.jopdesign.common.processormodel.AllocationModel; import com.jopdesign.common.processormodel.JOPConfig; import com.jopdesign.common.processormodel.JOPModel; import com.jopdesign.common.processormodel.JVMModel; import com.jopdesign.common.processormodel.JamuthModel; import com.jopdesign.common.processormodel.ProcessorModel; import com.jopdesign.common.processormodel.ProcessorModel.Model; import com.jopdesign.common.tools.AppLoader; import com.jopdesign.common.tools.ClassWriter; import com.jopdesign.common.tools.SourceLineStorage; import com.jopdesign.common.type.MemberID; import org.apache.bcel.util.ClassPath; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TreeMap; /** * This class is a helper used for creating and setting up the AppInfo class as well as for common * configuration tasks. * * @author Stefan Hepp (stefan@stefant.org) */ public class AppSetup { /** * Helper to load a property file in the package of a given class. * * @param rsClass the class for which the property file should be loaded. * @param filename the filename of the property file. * @return the loaded property file. * @throws IOException on read errors. */ public static Properties loadResourceProps(Class rsClass, String filename) throws IOException { return loadResourceProps(rsClass, filename, null); } /** * Helper to load a property file in the package of a given class. * * @param rsClass the class for which the property file should be loaded. * @param filename the filename of the property file. * @param defaultProps default properties to use for the new properties. * @return the loaded property file. * @throws IOException on read errors. */ public static Properties loadResourceProps(Class rsClass, String filename, Properties defaultProps) throws IOException { Properties p = new Properties(defaultProps); InputStream is = rsClass.getResourceAsStream(filename); if ( is == null ) { throw new IOException("Unable to find resource '"+filename+"' for class '"+rsClass.getCanonicalName()+"'."); } p.load(new BufferedInputStream(is)); is.close(); return p; } private Config config; private LogConfig logConfig; private AppInfo appInfo; private boolean handleAppInfoInit; private boolean loadSystemProps; private String programName; private String usageDescription; private String optionSyntax; private String versionInfo; private String configFilename; private MemberID mainMethodID; private Map<String, JopTool> tools; private Map<String, BooleanOption> optionalTools; /** * Initialize a new AppSetup with no default properties. * <p> * Tools however can add their own default config (see {@link JopTool#getDefaultProperties()}. * </p> * @see #AppSetup(Properties, boolean) */ public AppSetup() { // we do not want to load system props per default! // This ensures that the defaults are only defined by the tools and // execution on different machines does not differ due to different 'hidden' configurations. this(null, false); } /** * Initialize a new AppSetup and set the given (application specific) default properties. * * @param defaultProps defaults for the application, can overwrite tool defaults. * @param loadSystemProps if true, add all JVM system properties to the default properties. */ public AppSetup(Properties defaultProps, boolean loadSystemProps) { this.loadSystemProps = loadSystemProps; Properties def = new Properties(); if ( defaultProps != null ) { def.putAll(defaultProps); } if ( loadSystemProps ) { def.putAll(System.getProperties()); } config = new Config(def); // using default configuration here appInfo = AppInfo.getSingleton(); logConfig = new LogConfig(); tools = new TreeMap<String, JopTool>(); optionalTools = new LinkedHashMap<String,BooleanOption>(); } /** * Initialize this AppSetup, and load the classes into AppInfo. * <p> * Use {@link #registerTool(String, JopTool)} first to add all tools you want to use, as well as * {@link #setUsageInfo(String, String)}, {@link #setConfigFilename(String)} and {@link #setVersionInfo(String)} * if required. * </p> * @param args the commandline arguments * @param initReports if true, add options and initialize html report loggers. * @param allowExcludes if true, add options to allow to exclude packages or classes from loading. * @param writeClassOption if true, add options to write classes using {@link #writeClasses()} * @return a new AppSetup which has initialized AppInfo. */ public AppInfo initAndLoad(String[] args, boolean initReports, boolean allowExcludes, boolean writeClassOption) { addStandardOptions(true, true, initReports); addPackageOptions(allowExcludes); addWriteOptions(writeClassOption); String[] remain = setupConfig(args); setupLogger(initReports); setupAppInfo(remain, true); return appInfo; } public Config getConfig() { return config; } public OptionGroup getDebugGroup() { return config.getDebugGroup(); } public LogConfig getLoggerConfig() { return logConfig; } public AppInfo getAppInfo() { return appInfo; } /** * Get the main method signature, recognized by parsing commandline and config options. * This is available after {@link #setupConfig(String[])}, but does not check if the main * class exists. * * @return the signature of the main method, as set by the options. */ public MemberID getMainMethodID() { return mainMethodID; } /** * Register a tool and its AppEventHandler to AppInfo and AppSetup. * <p> * Event handlers will be called in the same order the tools are registered, so * the order of the registrations might be important. * </p> * @param name the unique name of the tool * @param jopTool the tool to register */ public void registerTool(String name, JopTool jopTool) { registerTool(name, jopTool, false, false); } /** * Register a tool and its AppEventHandler to AppInfo and AppSetup. * <p> * Event handlers will be called in the same order the tools are registered, so * the order of the registrations might be important. * </p> * If the tool is registered as optional, an additional option {@code use-<toolname>} will be * added, and the tool will only be initialized and used if the option is set. * * @param name the unique name of the tool * @param jopTool the tool to register * @param optional make the tool optional and add an option to config. * @param useDefault default value for the 'use' option if the tool is optional */ public void registerTool(String name, JopTool jopTool, boolean optional, boolean useDefault) { tools.put(name, jopTool); if (optional) { BooleanOption option = new BooleanOption("use-"+name, "Use the "+name+" tool", useDefault); config.addOption(option); optionalTools.put(name, option); config.setEnableOption(option.getKey()); } else { config.unsetEnableOption(); } // setup defaults and config try { Properties defaults = jopTool.getDefaultProperties(); if ( defaults != null ) { config.addDefaults(defaults, false); } } catch (IOException e) { System.err.println("Error loading default configuration file: "+e.getMessage()); System.exit(1); } // setup options jopTool.registerOptions(config); config.unsetEnableOption(); } /** * Check if a given tool is enabled. * @param name the name of the tool used to register it. * @return true if the tool is enabled. */ public boolean useTool(String name) { if (!tools.containsKey(name)) return false; if (!optionalTools.containsKey(name)) return true; return config.getOption(optionalTools.get(name)); } /** * Add some standard options to the config. * * @param stdOptions if true, add options defined in {@link Config#standardOptions}. * @param setupAppInfo if true, this will also add common setup options for AppInfo used by {@link #setupAppInfo} * @param setupReports if true, add options to setup reports */ public void addStandardOptions(boolean stdOptions, boolean setupAppInfo, boolean setupReports) { this.handleAppInfoInit = setupAppInfo; if (stdOptions) { config.addOptions(Config.standardOptions); } if (setupAppInfo) { config.addOption(Config.CLASSPATH); config.addOption(Config.ROOTS); config.addOption(Config.CALLSTRING_LENGTH); config.addOption(Config.MAIN_METHOD_NAME); config.addOption(Config.HW_OBJECTS); addProcessorModelOptions(); getDebugGroup().addOptions(Config.debugOptions); } if (setupReports) { config.addOption(Config.REPORTDIR); config.addOption(Config.ERROR_LOG_FILE); config.addOption(Config.INFO_LOG_FILE); } } private void addProcessorModelOptions() { config.addOption(Config.PROCESSOR_MODEL); JOPConfig.registerOptions(config); } /** * Add options to classify classes and packages and optionally exclude * them from the loader. * * @param addExcludeOptions if true, add options to exclude classes from loading. */ public void addPackageOptions(boolean addExcludeOptions) { config.addOption(Config.LIBRARY_CLASSES); if ( addExcludeOptions ) { config.addOption(Config.IGNORE_CLASSES); config.addOption(Config.EXCLUDE_LIBRARIES); config.addOption(Config.EXCLUDE_NATIVES); } } /** * Add option {@link Config#WRITE_PATH} for the default output path * and options for the ClassWriter used in {@link #writeClasses()}. * * @param writeClasses if true, add options for writing classfiles. */ public void addWriteOptions(boolean writeClasses) { config.addOption(Config.WRITE_PATH); if ( writeClasses ) { config.addOption(Config.WRITE_CLASSPATH); } } /** * Add options to load and store instruction source line infos which have a different source file * than the class to a file. Loading and writing classes using AppSetup will then load the configured * file automatically. * * @param writeOption add option for writing too, else only a load option is added. */ public void addSourceLineOptions(boolean writeOption) { config.addOption(Config.LOAD_SOURCELINES); if (writeOption) { config.addOption(Config.WRITE_SOURCELINES); } } /** * Set some usage infos. * * @param programName the executable program name * @param description a short additional usage description text */ public void setUsageInfo(String programName, String description) { setUsageInfo(programName, description, null); } /** * Set some usage infos. * * @param programName the executable program name * @param description a short additional usage description text * @param optionSyntax overwrite the generated program options syntax string. */ public void setUsageInfo(String programName, String description, String optionSyntax) { this.programName = programName; usageDescription = description; this.optionSyntax = optionSyntax; } /** * Add some text written out before the tool versions for --version. * @param versionInfo some version info text for the application. */ public void setVersionInfo(String versionInfo) { this.versionInfo = versionInfo; } /** * Get the base filename of the user-provided application configuration file. * * @return the configuration file without directory prefix. */ public String getConfigFilename() { return configFilename; } /** * Set the filename of the user-provided configuration file to load if it exists. * <p> * There is only one user-configfile for the whole application, not for each tool. * </p> * * @param configFilename the name of the config file for this app without path prefix */ public void setConfigFilename(String configFilename) { this.configFilename = configFilename; } /** * Load the config file, parse and check options. * * @param args cmdline arguments to parse * @return arguments not consumed. */ public String[] setupConfig(String[] args) { if ( configFilename != null ) { File file = findConfigFile(configFilename); if ( file != null && file.exists() ) { try { InputStream is = new BufferedInputStream(new FileInputStream(file)); config.addProperties(is); is.close(); } catch (FileNotFoundException e) { // should never happen System.out.flush(); System.err.println("Configuration file '"+configFilename+"' not found: "+e.getMessage()); } catch (IOException e) { System.out.flush(); System.err.println("Could not read config file '"+file+"': "+e.getMessage()); System.exit(3); } } } String[] rest = null; try { rest = config.parseArguments(args); // TODO options of non-enabled tools should not be required (but checked if present?) config.checkOptions(); // we parse the main method signature here, so it is available to the tools before // AppInfo is initialized mainMethodID = getMainSignature(rest.length > 0 ? rest[0] : null); } catch (Config.BadConfigurationException e) { System.out.flush(); System.err.println(e.getMessage()); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } // handle standard options if ( config.getOption(Config.SHOW_HELP) ) { printUsage(); System.exit(0); } if ( config.getOption(Config.SHOW_VERSION) ) { printVersion(); System.exit(0); } // let modules process their config options try { for (String tool : tools.keySet()) { if (useTool(tool)) { tools.get(tool).onSetupConfig(this); } } } catch (Config.BadConfigurationException e) { System.out.flush(); System.err.println(e.getMessage()); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } // Dump the config only after we let the tools initialize the config first if ( config.getOption(Config.SHOW_CONFIG) ) { config.printConfiguration(config.getDefaultIndent()); System.exit(0); } return rest; } /** * Setup AppInfo using the config previously initialized with {@link #setupConfig(String[])}. * * @param args the arguments containing the name of the main method and additional roots without config options. * @param loadTransitiveHull if true, load the transitive hull of the root classes too. */ public void setupAppInfo(String[] args, boolean loadTransitiveHull) { CustomAttribute.registerDefaultReader(); appInfo.setClassPath(new ClassPath(config.getOption(Config.CLASSPATH))); appInfo.setExitOnMissingClass(!config.getOption(Config.VERBOSE)); // handle class loading options if set if ( config.hasOption(Config.LIBRARY_CLASSES) ) { List<String> libs = Config.splitStringList(config.getOption(Config.LIBRARY_CLASSES)); for (String lib : libs) { appInfo.addLibrary(lib.replaceAll("/", ".")); } } if ( config.hasOption(Config.IGNORE_CLASSES) ) { List<String> ignore = Config.splitStringList(config.getOption(Config.IGNORE_CLASSES)); for (String cls : ignore) { appInfo.addLibrary(cls.replaceAll("/", ".")); } } if ( config.hasOption(Config.EXCLUDE_LIBRARIES) ) { appInfo.setLoadLibraries(!config.getOption(Config.EXCLUDE_LIBRARIES)); } if ( config.hasOption(Config.EXCLUDE_NATIVES) ) { appInfo.setLoadNatives(!config.getOption(Config.EXCLUDE_NATIVES)); } appInfo.setCallstringLength(config.getOption(Config.CALLSTRING_LENGTH).intValue()); for (String hwObject : Config.splitStringList(config.getOption(Config.HW_OBJECTS))) { appInfo.addHwObjectName(hwObject); } if (getDebugGroup().isSet(Config.DUMP_CACHEKEY)) { appInfo.setDumpCacheKeyFile(getDebugGroup().getOption(Config.DUMP_CACHEKEY)); } // register handler for (String toolName : tools.keySet()) { if (useTool(toolName)) { AppEventHandler handler = tools.get(toolName).getEventHandler(); if ( handler != null ) { appInfo.registerEventHandler(handler); } } } if ( config.hasOption(Config.PROCESSOR_MODEL) ) { initProcessorModel(config.getOption(Config.PROCESSOR_MODEL)); } // add system classes as roots List<String> roots = Config.splitStringList(config.getOption(Config.ROOTS)); for (String root : roots) { ClassInfo rootInfo = appInfo.loadClass(root.replaceAll("/",".")); if ( rootInfo == null ) { System.out.flush(); System.err.println("Error loading root class '"+root+"'."); System.exit(4); } appInfo.addRoot(rootInfo); } // check arguments String mainClassName = null; if (args.length > 0 && ! "".equals(args[0])) { mainClassName = args[0]; } else if(config.hasOption(Config.MAIN_METHOD_NAME)){ mainClassName = MemberID.parse(config.getOption(Config.MAIN_METHOD_NAME)).getClassName(); } else { System.out.flush(); System.err.println("You need to specify a main class or entry method."); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } // try to find main entry method try { MethodInfo main = getMainMethod(mainClassName.replaceAll("/",".")); appInfo.setMainMethod(main); } catch (Config.BadConfigurationException e) { System.out.flush(); System.err.println(e.getMessage()); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } // load other root classes for (int i = 1; i < args.length; i++) { ClassInfo clsInfo = appInfo.loadClass(args[i].replaceAll("/",".")); appInfo.addRoot(clsInfo); } // notify the tools about the root classes try { for (String tool : tools.keySet()) { if (useTool(tool)) { tools.get(tool).onSetupRoots(this, appInfo); } } } catch (Config.BadConfigurationException e) { System.out.flush(); System.err.println(e.getMessage()); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } // load and initialize all app classes if (loadTransitiveHull) { loadClassInfos(); // register source line loader before other event handlers if ( config.hasOption(Config.LOAD_SOURCELINES) ) { String filename = config.getOption(Config.LOAD_SOURCELINES); if (filename != null && !"".equals(filename.trim())) { File storage = new File(filename); if (storage.exists()) { new SourceLineStorage(storage).loadSourceInfos(); } } } } // let modules process their config options try { for (String tool : tools.keySet()) { if (useTool(tool)) { tools.get(tool).onSetupAppInfo(this, appInfo); } } } catch (Config.BadConfigurationException e) { System.out.flush(); System.err.println(e.getMessage()); if ( config.getOptions().containsOption(Config.SHOW_HELP) ) { System.err.println("Use '--help' to show a usage message."); } System.exit(2); } } /** * Setup the logger. You may want to call {@link #setupConfig(String[])} first to * load commandline options. * * @see LogConfig#setupLogger(Config) * @param addReportLoggers if true, add html-report loggers writing to {@link Config#WRITE_PATH}. */ public void setupLogger(boolean addReportLoggers) { logConfig.setupLogger(config); if ( addReportLoggers ) { String outDir = config.getOption(Config.REPORTDIR) + File.separator; try { String errorFile = outDir + config.getOption(Config.ERROR_LOG_FILE); String infoFile = outDir + config.getOption(Config.INFO_LOG_FILE); logConfig.setReportLoggers(new File(errorFile), new File(infoFile)); } catch (IOException e) { System.out.flush(); System.err.println("Error creating log files: "+e.getMessage()); System.exit(4); } } } public void printUsage() { String optionDesc; OptionGroup options = config.getOptions(); if ( optionSyntax != null ) { optionDesc = " " + optionSyntax; } else { optionDesc = " [@<propertyfile>] <options>"; if ( options.hasCommands() ) { optionDesc += " <cmd> <cmd-options>"; } if ( handleAppInfoInit ) { optionDesc += " [--] [<main-method> [<additional-roots>]]"; } } System.out.print("Usage: "+ (programName != null ? programName : "")); System.out.println(optionDesc); System.out.println(); if ( usageDescription != null && !"".equals(usageDescription) ) { System.out.println(usageDescription); System.out.println(); } System.out.println("Available options:"); for (Option<?> option : options.availableOptions() ) { System.out.println(option.toString(config.getDefaultIndent(), options)); } System.out.println(); // TODO this does not handle sub-subgroups for (String name : options.availableSubgroups()) { System.out.println("Options in group " + name + ":"); OptionGroup group = options.getGroup(name); for (Option<?> option : group.availableOptions()) { System.out.println(option.toString(config.getDefaultIndent(), group)); } System.out.println(); } System.out.println("The @<filename> syntax can be used multiple times. Entries in the property-file"); System.out.println("overwrite previous options and can be overwritten by successive options."); System.out.println("Every property-file can contain additional log4j configuration options."); System.out.println(); if (config.hasOption(Config.MAIN_METHOD_NAME)) { System.out.println("If '--"+Config.MAIN_METHOD_NAME.getKey()+ "' specifies a fully-qualified method name, <main-method> is optional."); } if ( loadSystemProps && configFilename != null ) { System.out.println("Config values can be set in the JVM system properties and in '" + configFilename + "'"); System.out.println("in the working directory."); } else if ( configFilename != null ) { System.out.println("Config values can be set in '" + configFilename + "' in the working directory."); } else if ( loadSystemProps ) { System.out.println("Config values can be set in the JVM system properties."); } System.out.println(); } public void printVersion() { if (versionInfo != null && !"".equals(versionInfo)) { System.out.println(versionInfo); } for (String name : tools.keySet() ) { System.out.println(name + ": " + tools.get(name).getToolVersion()); } } /** * Write the AppInfo classes to the directory specified by the {@link Config#WRITE_CLASSPATH} option. */ public void writeClasses() { writeClasses(Config.WRITE_CLASSPATH); // writing the source line infos *after* all other classes have been written so that timestamp checks works if (config.hasOption(Config.WRITE_SOURCELINES)) { String filename = config.getOption(Config.WRITE_SOURCELINES); if (filename != null && !"".equals(filename.trim())) { File storage = new File(filename); new SourceLineStorage(storage).storeSourceInfos(); } } } /** * Write the AppInfo classes to the directory specified by the outDir option. * * @param outDir the option for the classfiles output directory. */ public void writeClasses(Option<String> outDir) { try { ClassWriter writer = new ClassWriter(); writer.write(config.getOption(outDir)); } catch (IOException e) { ClassWriter.logger.error("Failed to write classes: "+e.getMessage(), e); System.exit(5); } } private void loadClassInfos() { // We could use UsedCodeFinder here to load only reachable code, once it supports loading classes on the fly new AppLoader().loadAll(false); appInfo.reloadClassHierarchy(); } private void initProcessorModel(Model model) { ProcessorModel pm; switch (model) { case JOP: pm = new JOPModel(config); break; case jamuth: pm = new JamuthModel(config); break; case allocation: pm = new AllocationModel(config); break; case JVM: pm = new JVMModel(); break; default: throw new BadConfigurationError("Unknown processor model " + model); } appInfo.setProcessorModel(pm); // load referenced classes as roots for (String jvmClass : pm.getJVMClasses()) { ClassInfo rootInfo = appInfo.loadClass(jvmClass.replaceAll("/",".")); if ( rootInfo == null ) { System.err.println("Error loading JVM class '"+jvmClass+"'."); System.exit(4); } } if (appInfo.doLoadNatives()) { for (String nativeClass : pm.getNativeClasses()) { ClassInfo rootInfo = appInfo.loadClass(nativeClass.replaceAll("/",".")); if ( rootInfo == null ) { System.err.println("Error loading Native class '"+nativeClass+"'."); System.exit(4); } } } // we do not set the JVM and native classes as root anymore, instead we let the PM decide which roots we need for (String root : pm.getJVMRoots()) { MemberID mID = MemberID.parse(root); // make sure the class exists.. ClassInfo cls = appInfo.loadClass(mID.getClassName()); // Get the member and add it as root if (mID.hasMemberName()) { MethodInfo methodInfo = cls.getMethodInfo(mID); if (methodInfo == null) { System.err.println("Could not find JVM root "+root); System.exit(5); } appInfo.addRoot(methodInfo); } else { appInfo.addRoot(cls); } } } private File findConfigFile(String configFile) { if ( configFile == null || "".equals(configFile) ) { return null; } // look in different paths? load multiple files? return new File(configFile); } private MemberID getMainSignature(String signature) throws BadConfigurationException { MemberID sMain; ClassPath path = new ClassPath(config.getOption(Config.CLASSPATH)); MemberID sMainMethod = MemberID.parse(config.getOption(Config.MAIN_METHOD_NAME), path); if (signature == null || "".equals(signature)) { sMain = sMainMethod; } else { // try to parse the signature sMain = MemberID.parse(signature, path); // use --mm if only main class has been given if (!sMain.hasMemberName()) { if (!sMainMethod.hasMemberName()) { throw new BadConfigurationException("Option '"+Config.MAIN_METHOD_NAME.getKey() +"' needs to specify a method name."); } sMain = new MemberID(sMain.getClassName(), sMainMethod.getMemberName(), sMainMethod.getDescriptor()); } } return sMain; } private MethodInfo getMainMethod(String signature) throws Config.BadConfigurationException { ClassInfo clsInfo; String clsName; MemberID sMain = getMainSignature(signature); clsName = sMain.getClassName(); if ( clsName == null ) { throw new BadConfigurationException("You need to specify a classname for the main method."); } try { clsInfo = appInfo.loadClass(clsName, true, false); } catch (ClassInfoNotFoundException e) { throw new BadConfigurationException("Class for '"+signature+"' could not be loaded: " + e.getMessage(), e); } // check if we have a full signature if (sMain.hasMethodSignature()) { MethodInfo method = clsInfo.getMethodInfo(sMain.getMethodSignature()); if (method == null) { throw new BadConfigurationException("Method '"+sMain.getMethodSignature()+"' not found in '" +clsName+"'."); } return method; } // try to find main method String mainName = sMain.getMemberName(); if ( mainName == null ) { mainName = config.getOption(Config.MAIN_METHOD_NAME); if(mainName != null) { mainName = MemberID.parse(mainName).getMethodSignature(); } } Collection<MethodInfo> methods = clsInfo.getMethodByName(mainName); if ( methods.isEmpty() ) { throw new BadConfigurationException("'No method '"+mainName+"' found in '"+clsName+"'."); } if ( methods.size() > 1 ) { StringBuilder s = new StringBuilder(String.format( "Multiple candidates for '%s' in '%s', please specify with a signature: ", mainName, clsName)); for (MethodInfo m : methods) { s.append("\n"); s.append(m.getMemberID()); } throw new BadConfigurationException(s.toString()); } return methods.iterator().next(); } }