001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hbase; 020 021import java.io.File; 022import java.io.FileFilter; 023import java.io.FileInputStream; 024import java.io.IOException; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.Enumeration; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Set; 031import java.util.jar.JarEntry; 032import java.util.jar.JarInputStream; 033import java.util.regex.Matcher; 034import java.util.regex.Pattern; 035 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039/** 040 * A class that finds a set of classes that are locally accessible 041 * (from .class or .jar files), and satisfy the conditions that are 042 * imposed by name and class filters provided by the user. 043 */ 044public class ClassFinder { 045 private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class); 046 private static String CLASS_EXT = ".class"; 047 048 private ResourcePathFilter resourcePathFilter; 049 private FileNameFilter fileNameFilter; 050 private ClassFilter classFilter; 051 private FileFilter fileFilter; 052 053 public interface ResourcePathFilter { 054 boolean isCandidatePath(String resourcePath, boolean isJar); 055 } 056 057 public interface FileNameFilter { 058 boolean isCandidateFile(String fileName, String absFilePath); 059 } 060 061 public interface ClassFilter { 062 boolean isCandidateClass(Class<?> c); 063 } 064 065 public static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter { 066 private ResourcePathFilter resourcePathFilter; 067 private FileNameFilter fileNameFilter; 068 private ClassFilter classFilter; 069 070 public Not(ResourcePathFilter resourcePathFilter){this.resourcePathFilter = resourcePathFilter;} 071 public Not(FileNameFilter fileNameFilter){this.fileNameFilter = fileNameFilter;} 072 public Not(ClassFilter classFilter){this.classFilter = classFilter;} 073 074 @Override 075 public boolean isCandidatePath(String resourcePath, boolean isJar) { 076 return !resourcePathFilter.isCandidatePath(resourcePath, isJar); 077 } 078 @Override 079 public boolean isCandidateFile(String fileName, String absFilePath) { 080 return !fileNameFilter.isCandidateFile(fileName, absFilePath); 081 } 082 @Override 083 public boolean isCandidateClass(Class<?> c) { 084 return !classFilter.isCandidateClass(c); 085 } 086 } 087 088 public static class And implements ClassFilter, ResourcePathFilter { 089 ClassFilter[] classFilters; 090 ResourcePathFilter[] resourcePathFilters; 091 092 public And(ClassFilter...classFilters) { this.classFilters = classFilters; } 093 public And(ResourcePathFilter... resourcePathFilters) { 094 this.resourcePathFilters = resourcePathFilters; 095 } 096 097 @Override 098 public boolean isCandidateClass(Class<?> c) { 099 for (ClassFilter filter : classFilters) { 100 if (!filter.isCandidateClass(c)) { 101 return false; 102 } 103 } 104 return true; 105 } 106 107 @Override public boolean isCandidatePath(String resourcePath, boolean isJar) { 108 for (ResourcePathFilter filter : resourcePathFilters) { 109 if (!filter.isCandidatePath(resourcePath, isJar)) { 110 return false; 111 } 112 } 113 return true; 114 } 115 } 116 117 public ClassFinder() { 118 this(null, null, null); 119 } 120 121 public ClassFinder(ResourcePathFilter resourcePathFilter, 122 FileNameFilter fileNameFilter, ClassFilter classFilter) { 123 this.resourcePathFilter = resourcePathFilter; 124 this.classFilter = classFilter; 125 this.fileNameFilter = fileNameFilter; 126 this.fileFilter = new FileFilterWithName(fileNameFilter); 127 } 128 129 /** 130 * Finds the classes in current package (of ClassFinder) and nested packages. 131 * @param proceedOnExceptions whether to ignore exceptions encountered for 132 * individual jars/files/classes, and proceed looking for others. 133 */ 134 public Set<Class<?>> findClasses(boolean proceedOnExceptions) 135 throws ClassNotFoundException, IOException, LinkageError { 136 return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions); 137 } 138 139 /** 140 * Finds the classes in a package and nested packages. 141 * @param packageName package names 142 * @param proceedOnExceptions whether to ignore exceptions encountered for 143 * individual jars/files/classes, and proceed looking for others. 144 */ 145 public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions) 146 throws ClassNotFoundException, IOException, LinkageError { 147 final String path = packageName.replace('.', '/'); 148 final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$"); 149 150 Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path); 151 List<File> dirs = new ArrayList<>(); 152 List<String> jars = new ArrayList<>(); 153 154 while (resources.hasMoreElements()) { 155 URL resource = resources.nextElement(); 156 String resourcePath = resource.getFile(); 157 Matcher matcher = jarResourceRe.matcher(resourcePath); 158 boolean isJar = matcher.find(); 159 resourcePath = isJar ? matcher.group(1) : resourcePath; 160 if (null == this.resourcePathFilter 161 || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) { 162 LOG.debug("Looking in " + resourcePath + "; isJar=" + isJar); 163 if (isJar) { 164 jars.add(resourcePath); 165 } else { 166 dirs.add(new File(resourcePath)); 167 } 168 } 169 } 170 171 Set<Class<?>> classes = new HashSet<>(); 172 for (File directory : dirs) { 173 classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions)); 174 } 175 for (String jarFileName : jars) { 176 classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions)); 177 } 178 return classes; 179 } 180 181 private Set<Class<?>> findClassesFromJar(String jarFileName, 182 String packageName, boolean proceedOnExceptions) 183 throws IOException, ClassNotFoundException, LinkageError { 184 JarInputStream jarFile = null; 185 try { 186 jarFile = new JarInputStream(new FileInputStream(jarFileName)); 187 } catch (IOException ioEx) { 188 LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx); 189 throw ioEx; 190 } 191 192 Set<Class<?>> classes = new HashSet<>(); 193 JarEntry entry = null; 194 try { 195 while (true) { 196 try { 197 entry = jarFile.getNextJarEntry(); 198 } catch (IOException ioEx) { 199 if (!proceedOnExceptions) { 200 throw ioEx; 201 } 202 LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx); 203 break; 204 } 205 if (entry == null) { 206 break; // loop termination condition 207 } 208 209 String className = entry.getName(); 210 if (!className.endsWith(CLASS_EXT)) { 211 continue; 212 } 213 int ix = className.lastIndexOf('/'); 214 String fileName = (ix >= 0) ? className.substring(ix + 1) : className; 215 if (null != this.fileNameFilter 216 && !this.fileNameFilter.isCandidateFile(fileName, className)) { 217 continue; 218 } 219 className = 220 className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.'); 221 if (!className.startsWith(packageName)) { 222 continue; 223 } 224 Class<?> c = makeClass(className, proceedOnExceptions); 225 if (c != null) { 226 if (!classes.add(c)) { 227 LOG.warn("Ignoring duplicate class " + className); 228 } 229 } 230 } 231 return classes; 232 } finally { 233 jarFile.close(); 234 } 235 } 236 237 private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName, 238 boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError { 239 Set<Class<?>> classes = new HashSet<>(); 240 if (!baseDirectory.exists()) { 241 LOG.warn(baseDirectory.getAbsolutePath() + " does not exist"); 242 return classes; 243 } 244 245 File[] files = baseDirectory.listFiles(this.fileFilter); 246 if (files == null) { 247 LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath()); 248 return classes; 249 } 250 251 for (File file : files) { 252 final String fileName = file.getName(); 253 if (file.isDirectory()) { 254 classes.addAll(findClassesFromFiles(file, packageName + "." + fileName, 255 proceedOnExceptions)); 256 } else { 257 String className = packageName + '.' 258 + fileName.substring(0, fileName.length() - CLASS_EXT.length()); 259 Class<?> c = makeClass(className, proceedOnExceptions); 260 if (c != null) { 261 if (!classes.add(c)) { 262 LOG.warn("Ignoring duplicate class " + className); 263 } 264 } 265 } 266 } 267 return classes; 268 } 269 270 private Class<?> makeClass(String className, boolean proceedOnExceptions) 271 throws ClassNotFoundException, LinkageError { 272 try { 273 Class<?> c = Class.forName(className, false, this.getClass().getClassLoader()); 274 boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c); 275 return isCandidateClass ? c : null; 276 } catch (NoClassDefFoundError|ClassNotFoundException classNotFoundEx) { 277 if (!proceedOnExceptions) { 278 throw classNotFoundEx; 279 } 280 LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx); 281 } catch (LinkageError linkageEx) { 282 if (!proceedOnExceptions) { 283 throw linkageEx; 284 } 285 LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx); 286 } 287 return null; 288 } 289 290 private static class FileFilterWithName implements FileFilter { 291 private FileNameFilter nameFilter; 292 293 public FileFilterWithName(FileNameFilter nameFilter) { 294 this.nameFilter = nameFilter; 295 } 296 297 @Override 298 public boolean accept(File file) { 299 return file.isDirectory() 300 || (file.getName().endsWith(CLASS_EXT) 301 && (null == nameFilter 302 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath()))); 303 } 304 } 305}