/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.compiler; import java.io.File; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Functionality to build a list of every class that's in * the classpath as a class file and not a jar. Also the * ability to match pattens against the big list to get a * set of matching classes out. * */ public class ClassMatcher { public enum ClassNameMatchStatus { MATCH_FOUND, NO_WILDCARD_MATCH, NO_EXACT_MATCH, } /** List of .class files found in the classpath */ String m_classList = null; /** List of matches found after applying patterns */ SortedSet<String> m_classNameMatches = new TreeSet<String>(); /** * Add a pattern that matches classes from the classpath * and add any matching classnames to m_classNameMatches. * * The pattern is of the form "org.voltdb.Foo" but can * contain single and double asterisks in the style * of ant wildcards, such as "org.voltdb.**" or * "org.voltdb.*bar" or "org.volt**.Foo" */ public ClassNameMatchStatus addPattern(String classNamePattern) { boolean matchFound = false; if (m_classList == null) { m_classList = getClasspathClassFileNames(); } String preppedName = classNamePattern.trim(); // include only full classes // for nested classes, include the parent pattern int indexOfDollarSign = classNamePattern.indexOf('$'); if (indexOfDollarSign >= 0) { classNamePattern = classNamePattern.substring(0, indexOfDollarSign); } // Substitution order is critical. // Keep track of whether or not this is a wildcard expression. // '.' is specifically not a wildcard. String regExPreppedName = preppedName.replace(".", "[.]"); boolean isWildcard = regExPreppedName.contains("*"); if (isWildcard) { regExPreppedName = regExPreppedName.replace("**", "[\\w.\\$]+"); regExPreppedName = regExPreppedName.replace("*", "[\\w\\$]*"); } String regex = "^" + // (line start) regExPreppedName + "$"; // (line end) Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); Matcher matcher = pattern.matcher(m_classList); while (matcher.find()) { String match = matcher.group(); // skip nested classes; the base class will include them if (match.contains("$")) { continue; } matchFound = true; m_classNameMatches.add(match); } if (matchFound) { return ClassNameMatchStatus.MATCH_FOUND; } else { if (isWildcard) { return ClassNameMatchStatus.NO_WILDCARD_MATCH; } else { return ClassNameMatchStatus.NO_EXACT_MATCH; } } } /** * Return the set of matched classnames in lexographical order. */ public SortedSet<String> getMatchedClassList() { return m_classNameMatches; } /** * Empty the data structures of this class to save memory. */ public void clear() { m_classList = null; m_classNameMatches.clear(); } /** * Helper class to process a directory of classfiles. */ private static class Package { final File file; final Package parent; Package(Package parent, File file) { assert(file != null); this.file = file; this.parent = parent; } /** Return the "dot" name for the class */ String getJavaName() { String fullName = file.getName(); for (Package p = parent; p != null; p = p.parent) { String parentName = p.file.getName(); fullName = parentName + "." + fullName; } return fullName; } /** * Process all of the elements inside a directory * into sub-packages and classfiles, adding classfiles * to the given parameter, "classes". */ void process(Set<String> classes) { File[] files = file.listFiles(); for (File f : files) { String name = f.getName(); if (name.endsWith(".class")) { String className = name.substring(0, name.length() - ".class".length()); String javaName = getJavaName() + "." + className; classes.add(javaName); } else if (f.isDirectory()) { Package p = new Package(this, f); p.process(classes); } } } } /** * For a given classpath root, scan it for packages and classes, * adding all found classnames to the given "classes" param. */ private static void processPathPart(String path, Set<String> classes) { File rootFile = new File(path); if (rootFile.isDirectory() == false) { return; } File[] files = rootFile.listFiles(); for (File f : files) { // classes in the anonymous package if (f.getName().endsWith(".class")) { String className = f.getName(); // trim the trailing .class from the end className = className.substring(0, className.length() - ".class".length()); classes.add(className); } if (f.isDirectory()) { Package p = new Package(null, f); p.process(classes); } } } /** * Get a single string that contains all of the non-jar * classfiles in the current classpath, separated by * newlines. Classfiles are represented by their Java * "dot" names, not filenames. */ static String getClasspathClassFileNames() { String classpath = System.getProperty("java.class.path"); String[] pathParts = classpath.split(File.pathSeparator); Set<String> classes = new TreeSet<String>(); for (String part : pathParts) { processPathPart(part, classes); } StringBuilder sb = new StringBuilder(); for (String className : classes) { sb.append(className).append('\n'); } return sb.toString(); } }