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       if (!proceedOnExceptions) {
137         throw ioEx;
138       }
139       LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
140     }
141 
142     Set<Class<?>> classes = new HashSet<Class<?>>();
143     JarEntry entry = null;
144     while (true) {
145       try {
146         entry = jarFile.getNextJarEntry();
147       } catch (IOException ioEx) {
148         if (!proceedOnExceptions) {
149           throw ioEx;
150         }
151         LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
152         break;
153       }
154       if (entry == null) {
155         break; // loop termination condition
156       }
157 
158       String className = entry.getName();
159       if (!className.endsWith(CLASS_EXT)) {
160         continue;
161       }
162       int ix = className.lastIndexOf('/');
163       String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
164       if (null != this.fileNameFilter
165           && !this.fileNameFilter.isCandidateFile(fileName, className)) {
166         continue;
167       }
168       className = className
169           .substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
170       if (!className.startsWith(packageName)) {
171         continue;
172       }
173       Class<?> c = makeClass(className, proceedOnExceptions);
174       if (c != null) {
175         if (!classes.add(c)) {
176           LOG.warn("Ignoring duplicate class " + className);
177         }
178       }
179     }
180     return classes;
181   }
182 
183   private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
184       boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
185     Set<Class<?>> classes = new HashSet<Class<?>>();
186     if (!baseDirectory.exists()) {
187       LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
188       return classes;
189     }
190 
191     File[] files = baseDirectory.listFiles(this.fileFilter);
192     if (files == null) {
193       LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
194       return classes;
195     }
196 
197     for (File file : files) {
198       final String fileName = file.getName();
199       if (file.isDirectory()) {
200         classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
201             proceedOnExceptions));
202       } else {
203         String className = packageName + '.'
204             + fileName.substring(0, fileName.length() - CLASS_EXT.length());
205         Class<?> c = makeClass(className, proceedOnExceptions);
206         if (c != null) {
207           if (!classes.add(c)) {
208             LOG.warn("Ignoring duplicate class " + className);
209           }
210         }
211       }
212     }
213     return classes;
214   }
215 
216   private Class<?> makeClass(String className, boolean proceedOnExceptions)
217     throws ClassNotFoundException, LinkageError {
218     try {
219       Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
220       boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
221       return isCandidateClass ? c : null;
222     } catch (ClassNotFoundException classNotFoundEx) {
223       if (!proceedOnExceptions) {
224         throw classNotFoundEx;
225       }
226       LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
227     } catch (LinkageError linkageEx) {
228       if (!proceedOnExceptions) {
229         throw linkageEx;
230       }
231       LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
232     }
233     return null;
234   }
235 
236   private class FileFilterWithName implements FileFilter {
237     private FileNameFilter nameFilter;
238 
239     public FileFilterWithName(FileNameFilter nameFilter) {
240       this.nameFilter = nameFilter;
241     }
242 
243     @Override
244     public boolean accept(File file) {
245       return file.isDirectory()
246           || (file.getName().endsWith(CLASS_EXT)
247               && (null == nameFilter
248                 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
249     }
250   };
251 };