View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.io.File;
22  import java.io.FileFilter;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.net.URL;
26  import java.util.ArrayList;
27  import java.util.Enumeration;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarInputStream;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  
39  /**
40   * A class that finds a set of classes that are locally accessible
41   * (from .class or .jar files), and satisfy the conditions that are
42   * imposed by name and class filters provided by the user.
43   */
44  public class ClassFinder {
45    private static final Log LOG = LogFactory.getLog(ClassFinder.class);
46    private static String CLASS_EXT = ".class";
47  
48    private ResourcePathFilter resourcePathFilter;
49    private FileNameFilter fileNameFilter;
50    private ClassFilter classFilter;
51    private FileFilter fileFilter;
52  
53    public interface ResourcePathFilter {
54      boolean isCandidatePath(String resourcePath, boolean isJar);
55    };
56  
57    public interface FileNameFilter {
58      boolean isCandidateFile(String fileName, String absFilePath);
59    };
60  
61    public interface ClassFilter {
62      boolean isCandidateClass(Class<?> c);
63    };
64  
65    public ClassFinder() {
66      this(null, null, null);
67    }
68  
69    public ClassFinder(ResourcePathFilter resourcePathFilter,
70        FileNameFilter fileNameFilter, ClassFilter classFilter) {
71      this.resourcePathFilter = resourcePathFilter;
72      this.classFilter = classFilter;
73      this.fileNameFilter = fileNameFilter;
74      this.fileFilter = new FileFilterWithName(fileNameFilter);
75    }
76  
77    /**
78     * Finds the classes in current package (of ClassFinder) and nested packages.
79     * @param proceedOnExceptions whether to ignore exceptions encountered for
80     *        individual jars/files/classes, and proceed looking for others.
81     */
82    public Set<Class<?>> findClasses(boolean proceedOnExceptions)
83      throws ClassNotFoundException, IOException, LinkageError {
84      return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
85    }
86  
87    /**
88     * Finds the classes in a package and nested packages.
89     * @param packageName package names
90     * @param proceedOnExceptions whether to ignore exceptions encountered for
91     *        individual jars/files/classes, and proceed looking for others.
92     */
93    public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
94      throws ClassNotFoundException, IOException, LinkageError {
95      final String path = packageName.replace('.', '/');
96      final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
97  
98      Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
99      List<File> dirs = new ArrayList<File>();
100     List<String> jars = new ArrayList<String>();
101 
102     while (resources.hasMoreElements()) {
103       URL resource = resources.nextElement();
104       String resourcePath = resource.getFile();
105       Matcher matcher = jarResourceRe.matcher(resourcePath);
106       boolean isJar = matcher.find();
107       resourcePath = isJar ? matcher.group(1) : resourcePath;
108       if (null == this.resourcePathFilter
109           || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
110         LOG.debug("Will look for classes in " + resourcePath);
111         if (isJar) {
112           jars.add(resourcePath);
113         } else {
114           dirs.add(new File(resourcePath));
115         }
116       }
117     }
118 
119     Set<Class<?>> classes = new HashSet<Class<?>>();
120     for (File directory : dirs) {
121       classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
122     }
123     for (String jarFileName : jars) {
124       classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
125     }
126     return classes;
127   }
128 
129   private Set<Class<?>> findClassesFromJar(String jarFileName,
130       String packageName, boolean proceedOnExceptions)
131     throws IOException, ClassNotFoundException, LinkageError {
132     JarInputStream jarFile = null;
133     try {
134       jarFile = new JarInputStream(new FileInputStream(jarFileName));
135     } catch (IOException ioEx) {
136       LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
137       throw ioEx;
138     }
139 
140     Set<Class<?>> classes = new HashSet<Class<?>>();
141     JarEntry entry = null;
142     try {
143       while (true) {
144         try {
145           entry = jarFile.getNextJarEntry();
146         } catch (IOException ioEx) {
147           if (!proceedOnExceptions) {
148             throw ioEx;
149           }
150           LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
151           break;
152         }
153         if (entry == null) {
154           break; // loop termination condition
155         }
156 
157         String className = entry.getName();
158         if (!className.endsWith(CLASS_EXT)) {
159           continue;
160         }
161         int ix = className.lastIndexOf('/');
162         String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
163         if (null != this.fileNameFilter
164             && !this.fileNameFilter.isCandidateFile(fileName, className)) {
165           continue;
166         }
167         className =
168             className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
169         if (!className.startsWith(packageName)) {
170           continue;
171         }
172         Class<?> c = makeClass(className, proceedOnExceptions);
173         if (c != null) {
174           if (!classes.add(c)) {
175             LOG.warn("Ignoring duplicate class " + className);
176           }
177         }
178       }
179       return classes;
180     } finally {
181       jarFile.close();
182     }
183   }
184 
185   private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
186       boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
187     Set<Class<?>> classes = new HashSet<Class<?>>();
188     if (!baseDirectory.exists()) {
189       LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
190       return classes;
191     }
192 
193     File[] files = baseDirectory.listFiles(this.fileFilter);
194     if (files == null) {
195       LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
196       return classes;
197     }
198 
199     for (File file : files) {
200       final String fileName = file.getName();
201       if (file.isDirectory()) {
202         classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
203             proceedOnExceptions));
204       } else {
205         String className = packageName + '.'
206             + fileName.substring(0, fileName.length() - CLASS_EXT.length());
207         Class<?> c = makeClass(className, proceedOnExceptions);
208         if (c != null) {
209           if (!classes.add(c)) {
210             LOG.warn("Ignoring duplicate class " + className);
211           }
212         }
213       }
214     }
215     return classes;
216   }
217 
218   private Class<?> makeClass(String className, boolean proceedOnExceptions)
219     throws ClassNotFoundException, LinkageError {
220     try {
221       Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
222       boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
223       return isCandidateClass ? c : null;
224     } catch (ClassNotFoundException classNotFoundEx) {
225       if (!proceedOnExceptions) {
226         throw classNotFoundEx;
227       }
228       LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
229     } catch (LinkageError linkageEx) {
230       if (!proceedOnExceptions) {
231         throw linkageEx;
232       }
233       LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
234     }
235     return null;
236   }
237 
238   private class FileFilterWithName implements FileFilter {
239     private FileNameFilter nameFilter;
240 
241     public FileFilterWithName(FileNameFilter nameFilter) {
242       this.nameFilter = nameFilter;
243     }
244 
245     @Override
246     public boolean accept(File file) {
247       return file.isDirectory()
248           || (file.getName().endsWith(CLASS_EXT)
249               && (null == nameFilter
250                 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
251     }
252   };
253 };