/** * Copyright 2013 Alexey Ragozin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gridkit.jvmtool.cli; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; /** * Swiss Java Knife * <br/> * Command line tool for JVM troubleshooting * * @author Alexey Ragozin (alexey.ragozin@gmail.com) */ public class CommandLauncher { public interface CmdRef { public String getCommandName(); public Runnable newCommand(CommandLauncher host); } @Parameter(names = "--help", help = true) private boolean help = false; @Parameter(names = {"-X", "--verbose"}, description = "Enable detailed diagnostics") private boolean verbose = false; @Parameter(names = "--commands", help = true) private boolean listCommands = false; private boolean suppressSystemExit; private Map<String, Runnable> commands = new HashMap<String, Runnable>(); public boolean isVerbose() { return verbose; } public Error fail(String... messages) { throw new CommandAbortedError(false, messages); } public Error fail(String message, Exception e) { throw new CommandAbortedError(false, new String[]{message}, e); } public Error failAndPrintUsage(String... messages) { throw new CommandAbortedError(true, messages); } public void logError(String line) { System.err.println(line); } public void logTrace(Throwable e) { e.printStackTrace(System.err); } public void suppressSystemExit() { suppressSystemExit = true; } public boolean start(String[] args) { JCommander parser = null; try { parser = new JCommander(this); addCommands(parser); try { parser.parse(args); } catch(Exception e) { failAndPrintUsage(e.toString()); } if (help) { String cmd = parser.getParsedCommand(); if (cmd == null) { parser.usage(); } else { parser.usage(cmd); } } else if (listCommands) { for(String cmd: commands.keySet()) { System.out.println(String.format("%8s - %s", cmd, parser.getCommandDescription(cmd))); } } else { Runnable cmd = commands.get(parser.getParsedCommand()); if (cmd == null) { failAndPrintUsage(); } else { cmd.run(); } } if (suppressSystemExit) { return true; } else { System.exit(0); } } catch(CommandAbortedError error) { for(String m: error.messages) { logError(m); } if (verbose && error.getCause() != null) { logTrace(error.getCause()); } if (error.printUsage && parser != null) { if (parser.getParsedCommand() != null) { parser.usage(parser.getParsedCommand()); } else { parser.usage(); } } } catch(Throwable e) { e.printStackTrace(); } // abnormal termination if (suppressSystemExit) { return false; } else { System.exit(1); return false; } } protected List<String> getCommandPackages() { return Collections.singletonList(getClass().getPackage().getName() + ".cmd"); } private void addCommands(JCommander parser) throws InstantiationException, IllegalAccessException { for(String pack: getCommandPackages()) { for(Class<?> c: findClasses(pack)) { if (CmdRef.class.isAssignableFrom(c)) { CmdRef cmd = (CmdRef) c.newInstance(); String cmdName = cmd.getCommandName(); Runnable cmdTask = cmd.newCommand(this); if (commands.containsKey(cmdName)) { fail("Ambiguous implementation for '" + cmdName + "'"); } commands.put(cmdName, cmdTask); parser.addCommand(cmdName, cmdTask); } } } } private List<Class<?>> findClasses(String packageName) { List<Class<?>> result = new ArrayList<Class<?>>(); try { String path = packageName.replace('.', '/'); for(String f: findFiles(path)) { if (f.endsWith(".class") && f.indexOf('$') < 0) { f = f.substring(0, f.length() - ".class".length()); f = f.replace('/', '.'); result.add(Class.forName(f)); } } return result; } catch (Exception e) { throw new RuntimeException(e); } } static List<String> findFiles(String path) throws IOException { List<String> result = new ArrayList<String>(); ClassLoader cl = Thread.currentThread().getContextClassLoader(); Enumeration<URL> en = cl.getResources(path); while(en.hasMoreElements()) { URL u = en.nextElement(); listFiles(result, u, path); } return result; } static List<String> listFiles(List<String> results, URL packageURL, String path) throws IOException { if(packageURL.getProtocol().equals("jar")){ String jarFileName; JarFile jf ; Enumeration<JarEntry> jarEntries; String entryName; // build jar file name, then loop through zipped entries jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8"); jarFileName = jarFileName.substring(5,jarFileName.indexOf("!")); jf = new JarFile(jarFileName); jarEntries = jf.entries(); while(jarEntries.hasMoreElements()){ entryName = jarEntries.nextElement().getName(); if(entryName.startsWith(path)){ results.add(entryName); } } // loop through files in classpath }else{ File dir = new File(packageURL.getFile()); String cp = dir.getCanonicalPath(); File root = dir; while(true) { if (cp.equals(new File(root, path).getCanonicalPath())) { break; } root = root.getParentFile(); } listFiles(results, root, dir); } return results; } static void listFiles(List<String> names, File root, File dir) { String rootPath = root.getAbsolutePath(); if (dir.exists() && dir.isDirectory()) { for(File file: dir.listFiles()) { if (file.isDirectory()) { listFiles(names, root, file); } else { String name = file.getAbsolutePath().substring(rootPath.length() + 1); name = name.replace('\\', '/'); names.add(name); } } } } @SuppressWarnings("serial") public static class CommandAbortedError extends Error { public boolean printUsage; public String[] messages; public CommandAbortedError(boolean printUsage, String[] messages) { super(); this.printUsage = printUsage; this.messages = messages; } public CommandAbortedError(boolean printUsage, String[] messages, Exception e) { super(e); this.printUsage = printUsage; this.messages = messages; } } }