package org.wikibrain.utils; import org.apache.commons.io.FilenameUtils; import org.clapper.util.classutil.*; import org.wikibrain.conf.ProviderFilter; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.regex.Pattern; /** * @author Shilad Sen */ public class JvmUtils { /** * Wikibrain + the standard Geometry class */ private static Pattern WIKIBRAIN_CLASS_PATTERN = Pattern.compile("org.wikibrain.*|com.vividsolutions.jts.geom.Geometry"); /** * Ignores Jooq and anonymous classes. */ private static Pattern WIKIBRAIN_CLASS_BLACKLIST = Pattern.compile("(.*jooq.*|\\$[0-9]+$)"); private static final Logger LOG = LoggerFactory.getLogger(JvmUtils.class); public static final int MAX_FILE_SIZE = 8 * 1024 * 1024; // 8MB /** * Returns a carefully constructed classpath matching the current process. * @return */ public static String getClassPath() { String separator = System.getProperty("path.separator"); String classPath = System.getProperty("java.class.path", "."); // Try to get the URL class loader to make dynamic class loading work for Grails, etc. ClassLoader loader = JvmUtils.class.getClassLoader(); while (loader != null) { if (loader instanceof URLClassLoader) { for (URL url : ((URLClassLoader)loader).getURLs()) { if (isLocalFile(url)) { try { classPath += separator + new File(url.toURI()); } catch (URISyntaxException e) { LOG.warn("Illegal url: " + url); } } } } loader = loader.getParent(); } return classPath; } private static List<File> CLASS_PATH = null; public synchronized static List<File> getClassPathAsList() { if (CLASS_PATH != null) { return CLASS_PATH; } CLASS_PATH = new ArrayList<File>(); String separator = System.getProperty("path.separator"); for (String entry : getClassPath().split(separator)) { LOG.debug("considering classpath entry " + entry); File file = new File(entry); if (!file.exists()) { LOG.warn("skipping nonexistent classpath file " + file); continue; } CLASS_PATH.add(new File(FilenameUtils.normalize(file.getAbsolutePath()))); } return CLASS_PATH; } /** * Launches a new java program that uses the running configuration settings. * @param klass * @param args * @return * @throws IOException * @throws InterruptedException */ public static Process launch(Class klass, String args[]) throws IOException, InterruptedException { return launch(klass, args, System.out, System.err, null); } /** * Launches a new java program that uses the running configuration settings. * @param klass * @param args * @param out stdout stream for process * @param err stderr stream for process * @param heapSize Maximum heap memory (e.g. 4M, 4G, etc) or null to use existing setting. * @return * @throws IOException * @throws InterruptedException */ public static Process launch(Class klass, String args[], OutputStream out, OutputStream err, String heapSize) throws IOException, InterruptedException { JavaProcessBuilder builder = new JavaProcessBuilder(); RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); String heapArg = heapSize == null ? null : ("-Xmx" + heapSize); boolean foundHeapArg = false; for (String jvmArg : runtimeMxBean.getInputArguments()) { if (!jvmArg.startsWith("-")) { break; } if (heapArg != null && jvmArg.startsWith("-Xmx")) { foundHeapArg = true; jvmArg = heapArg; } if (!jvmArg.equals("-jar")) { builder.jvmArg(jvmArg); } } if (heapArg != null && !foundHeapArg) { builder.jvmArg(heapArg); } builder.classpath(getClassPath()); for (String arg : args) { builder.arg(arg); } builder.mainClass(klass.getName()); return builder.launch(out, err); } /** * Adapted from http://www.java2s.com/Code/Java/Network-Protocol/IsURLalocalfile.htm * @param url * @return */ private static boolean isLocalFile(URL url) { return ((url.getProtocol().equals("file")) && (url.getHost() == null || url.getHost().equals(""))); } /** * @return The maximum JVM heap size in Mbs, rounded down. */ private static int maxMemoryInMbs() { return (int) (Runtime.getRuntime().maxMemory() / (1024*1024)); } public static void setWikiBrainClassPattern(Pattern pattern, Pattern blacklist) { WIKIBRAIN_CLASS_PATTERN = pattern; WIKIBRAIN_CLASS_BLACKLIST = blacklist; } /** * Returns the first class in the classpath that has the specified short name. * For example, "LocalLink" -> org.wikibrain.core.LocalLink.class * * The full mapping between short names and full names is cached because it is costly to build it. * * @param shortName * @return Class, or null if no full classname matches the short name. */ public static Class classForShortName(String shortName) { String fullName = getFullClassName(shortName); if (fullName == null) { return null; } try { return Class.forName(fullName); } catch (ClassNotFoundException e) { throw new RuntimeException(e); // class came from the classpath, so this shouldn't happen } } private static Map<String, String> NAME_TO_CLASS = null; /** * Returns the first element in the classpath that has the specified short name. * For example, "LocalLink" -> "org.wikibrain.core.LocalLink" * The full mapping between short names and full names is cached because it is costly to build it. * * @param shortName * @return Full name, or null if no full name matches the short name. */ public synchronized static String getFullClassName(String shortName) { if (NAME_TO_CLASS != null) { return NAME_TO_CLASS.get(shortName); } NAME_TO_CLASS = new HashMap<String, String>(); for (File file : getClassPathAsList()) { if (file.length() > MAX_FILE_SIZE) { LOG.debug("skipping looking for providers in large file " + file); continue; } ClassFinder finder = new ClassFinder(); finder.add(file); ClassFilter filter = new AndClassFilter( new RegexClassFilter(WIKIBRAIN_CLASS_PATTERN.pattern()), new NotClassFilter(new RegexClassFilter(WIKIBRAIN_CLASS_BLACKLIST.pattern())) ); Collection<ClassInfo> foundClasses = new ArrayList<ClassInfo>(); finder.findClasses(foundClasses,filter); for (ClassInfo info : foundClasses) { String tokens[] = info.getClassName().split("[.]"); if (tokens.length == 0) { continue; // SHOULD NEVER HAPPEN } String n = tokens[tokens.length - 1]; if (!NAME_TO_CLASS.containsKey(n)) { NAME_TO_CLASS.put(n, info.getClassName()); } } } LOG.info("found " + NAME_TO_CLASS.size() + " classes when constructing short to full class name mapping"); // System.err.println(NAME_TO_CLASS); return NAME_TO_CLASS.get(shortName); } }