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 static class Not implements ResourcePathFilter, FileNameFilter, ClassFilter {
66      private ResourcePathFilter resourcePathFilter;
67      private FileNameFilter fileNameFilter;
68      private ClassFilter classFilter;
69  
70      public Not(ResourcePathFilter resourcePathFilter){this.resourcePathFilter = resourcePathFilter;}
71      public Not(FileNameFilter fileNameFilter){this.fileNameFilter = fileNameFilter;}
72      public Not(ClassFilter classFilter){this.classFilter = classFilter;}
73  
74      @Override
75      public boolean isCandidatePath(String resourcePath, boolean isJar) {
76        return !resourcePathFilter.isCandidatePath(resourcePath, isJar);
77      }
78      @Override
79      public boolean isCandidateFile(String fileName, String absFilePath) {
80        return !fileNameFilter.isCandidateFile(fileName, absFilePath);
81      }
82      @Override
83      public boolean isCandidateClass(Class<?> c) {
84        return !classFilter.isCandidateClass(c);
85      }
86    }
87  
88    public static class And implements ClassFilter {
89      ClassFilter[] classFilters;
90      public And(ClassFilter...classFilters) { this.classFilters = classFilters; }
91      @Override
92      public boolean isCandidateClass(Class<?> c) {
93        for (ClassFilter filter : classFilters) {
94          if (!filter.isCandidateClass(c)) {
95            return false;
96          }
97        }
98        return true;
99      }
100   }
101 
102   public ClassFinder() {
103     this(null, null, null);
104   }
105 
106   public ClassFinder(ResourcePathFilter resourcePathFilter,
107       FileNameFilter fileNameFilter, ClassFilter classFilter) {
108     this.resourcePathFilter = resourcePathFilter;
109     this.classFilter = classFilter;
110     this.fileNameFilter = fileNameFilter;
111     this.fileFilter = new FileFilterWithName(fileNameFilter);
112   }
113 
114   /**
115    * Finds the classes in current package (of ClassFinder) and nested packages.
116    * @param proceedOnExceptions whether to ignore exceptions encountered for
117    *        individual jars/files/classes, and proceed looking for others.
118    */
119   public Set<Class<?>> findClasses(boolean proceedOnExceptions)
120     throws ClassNotFoundException, IOException, LinkageError {
121     return findClasses(this.getClass().getPackage().getName(), proceedOnExceptions);
122   }
123 
124   /**
125    * Finds the classes in a package and nested packages.
126    * @param packageName package names
127    * @param proceedOnExceptions whether to ignore exceptions encountered for
128    *        individual jars/files/classes, and proceed looking for others.
129    */
130   public Set<Class<?>> findClasses(String packageName, boolean proceedOnExceptions)
131     throws ClassNotFoundException, IOException, LinkageError {
132     final String path = packageName.replace('.', '/');
133     final Pattern jarResourceRe = Pattern.compile("^file:(.+\\.jar)!/" + path + "$");
134 
135     Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(path);
136     List<File> dirs = new ArrayList<File>();
137     List<String> jars = new ArrayList<String>();
138 
139     while (resources.hasMoreElements()) {
140       URL resource = resources.nextElement();
141       String resourcePath = resource.getFile();
142       Matcher matcher = jarResourceRe.matcher(resourcePath);
143       boolean isJar = matcher.find();
144       resourcePath = isJar ? matcher.group(1) : resourcePath;
145       if (null == this.resourcePathFilter
146           || this.resourcePathFilter.isCandidatePath(resourcePath, isJar)) {
147         LOG.debug("Will look for classes in " + resourcePath);
148         if (isJar) {
149           jars.add(resourcePath);
150         } else {
151           dirs.add(new File(resourcePath));
152         }
153       }
154     }
155 
156     Set<Class<?>> classes = new HashSet<Class<?>>();
157     for (File directory : dirs) {
158       classes.addAll(findClassesFromFiles(directory, packageName, proceedOnExceptions));
159     }
160     for (String jarFileName : jars) {
161       classes.addAll(findClassesFromJar(jarFileName, packageName, proceedOnExceptions));
162     }
163     return classes;
164   }
165 
166   private Set<Class<?>> findClassesFromJar(String jarFileName,
167       String packageName, boolean proceedOnExceptions)
168     throws IOException, ClassNotFoundException, LinkageError {
169     JarInputStream jarFile = null;
170     try {
171       jarFile = new JarInputStream(new FileInputStream(jarFileName));
172     } catch (IOException ioEx) {
173       LOG.warn("Failed to look for classes in " + jarFileName + ": " + ioEx);
174       throw ioEx;
175     }
176 
177     Set<Class<?>> classes = new HashSet<Class<?>>();
178     JarEntry entry = null;
179     try {
180       while (true) {
181         try {
182           entry = jarFile.getNextJarEntry();
183         } catch (IOException ioEx) {
184           if (!proceedOnExceptions) {
185             throw ioEx;
186           }
187           LOG.warn("Failed to get next entry from " + jarFileName + ": " + ioEx);
188           break;
189         }
190         if (entry == null) {
191           break; // loop termination condition
192         }
193 
194         String className = entry.getName();
195         if (!className.endsWith(CLASS_EXT)) {
196           continue;
197         }
198         int ix = className.lastIndexOf('/');
199         String fileName = (ix >= 0) ? className.substring(ix + 1) : className;
200         if (null != this.fileNameFilter
201             && !this.fileNameFilter.isCandidateFile(fileName, className)) {
202           continue;
203         }
204         className =
205             className.substring(0, className.length() - CLASS_EXT.length()).replace('/', '.');
206         if (!className.startsWith(packageName)) {
207           continue;
208         }
209         Class<?> c = makeClass(className, proceedOnExceptions);
210         if (c != null) {
211           if (!classes.add(c)) {
212             LOG.warn("Ignoring duplicate class " + className);
213           }
214         }
215       }
216       return classes;
217     } finally {
218       jarFile.close();
219     }
220   }
221 
222   private Set<Class<?>> findClassesFromFiles(File baseDirectory, String packageName,
223       boolean proceedOnExceptions) throws ClassNotFoundException, LinkageError {
224     Set<Class<?>> classes = new HashSet<Class<?>>();
225     if (!baseDirectory.exists()) {
226       LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
227       return classes;
228     }
229 
230     File[] files = baseDirectory.listFiles(this.fileFilter);
231     if (files == null) {
232       LOG.warn("Failed to get files from " + baseDirectory.getAbsolutePath());
233       return classes;
234     }
235 
236     for (File file : files) {
237       final String fileName = file.getName();
238       if (file.isDirectory()) {
239         classes.addAll(findClassesFromFiles(file, packageName + "." + fileName,
240             proceedOnExceptions));
241       } else {
242         String className = packageName + '.'
243             + fileName.substring(0, fileName.length() - CLASS_EXT.length());
244         Class<?> c = makeClass(className, proceedOnExceptions);
245         if (c != null) {
246           if (!classes.add(c)) {
247             LOG.warn("Ignoring duplicate class " + className);
248           }
249         }
250       }
251     }
252     return classes;
253   }
254 
255   private Class<?> makeClass(String className, boolean proceedOnExceptions)
256     throws ClassNotFoundException, LinkageError {
257     try {
258       Class<?> c = Class.forName(className, false, this.getClass().getClassLoader());
259       boolean isCandidateClass = null == classFilter || classFilter.isCandidateClass(c);
260       return isCandidateClass ? c : null;
261     } catch (ClassNotFoundException classNotFoundEx) {
262       if (!proceedOnExceptions) {
263         throw classNotFoundEx;
264       }
265       LOG.debug("Failed to instantiate or check " + className + ": " + classNotFoundEx);
266     } catch (LinkageError linkageEx) {
267       if (!proceedOnExceptions) {
268         throw linkageEx;
269       }
270       LOG.debug("Failed to instantiate or check " + className + ": " + linkageEx);
271     }
272     return null;
273   }
274 
275   private class FileFilterWithName implements FileFilter {
276     private FileNameFilter nameFilter;
277 
278     public FileFilterWithName(FileNameFilter nameFilter) {
279       this.nameFilter = nameFilter;
280     }
281 
282     @Override
283     public boolean accept(File file) {
284       return file.isDirectory()
285           || (file.getName().endsWith(CLASS_EXT)
286               && (null == nameFilter
287                 || nameFilter.isCandidateFile(file.getName(), file.getAbsolutePath())));
288     }
289   };
290 };