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, ResourcePathFilter {
89      ClassFilter[] classFilters;
90      ResourcePathFilter[] resourcePathFilters;
91  
92      public And(ClassFilter...classFilters) { this.classFilters = classFilters; }
93      public And(ResourcePathFilter... resourcePathFilters) {
94        this.resourcePathFilters = resourcePathFilters;
95      }
96  
97      @Override
98      public boolean isCandidateClass(Class<?> c) {
99        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<File>();
152     List<String> jars = new ArrayList<String>();
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("Will look for classes in " + resourcePath);
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<Class<?>>();
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<Class<?>>();
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<Class<?>>();
240     if (!baseDirectory.exists()) {
241       LOG.warn("Failed to find " + baseDirectory.getAbsolutePath());
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 (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 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 };