| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.giraph.utils; |
| |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarInputStream; |
| |
| /** |
| * Helper class to deal with annotations in runtime. |
| */ |
| public class AnnotationUtils { |
| /** Do not instantiate. */ |
| private AnnotationUtils() { |
| } |
| |
| /** |
| * Finds all classes within a package which are annotated with certain |
| * annotation. |
| * |
| * @param annotation Annotation which we are looking for |
| * @param <T> Annotation class |
| * @param packageName Package in which to search |
| * @return The list of annotated classes |
| */ |
| public static <T extends Annotation> List<Class<?>> getAnnotatedClasses( |
| Class<T> annotation, String packageName) { |
| ArrayList<Class<?>> ret = new ArrayList<Class<?>>(); |
| for (Iterator<Class<?>> it = getClassesIterator(packageName); |
| it.hasNext();) { |
| Class<?> clazz = it.next(); |
| if (clazz.getAnnotation(annotation) != null) { |
| ret.add(clazz); |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * @param packageName Package through which to iterate |
| * @return Iterator through the classes of this jar file (if executed from |
| * jar) or through the classes of org package (if .class file is |
| * executed) |
| */ |
| public static Iterator<Class<?>> getClassesIterator(String packageName) { |
| if (isExecutedFromJar()) { |
| try { |
| return new JarClassesIterator(packageName); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } else { |
| return new GeneralClassesIterator(packageName); |
| } |
| } |
| |
| /** |
| * @return Whether or not the code is executed from jar file |
| */ |
| private static boolean isExecutedFromJar() { |
| return AnnotationUtils.class.getResource("AnnotationUtils.class") |
| .getProtocol().equals("jar"); |
| } |
| |
| /** |
| * To be used when {@link #isExecutedFromJar()} is true. |
| * |
| * @return If executed from jar file returns the path to the jar |
| * (otherwise it will return the folder in which this .class file is |
| * located) |
| */ |
| private static String getCurrentJar() { |
| return AnnotationUtils.class.getProtectionDomain().getCodeSource() |
| .getLocation().getFile(); |
| } |
| |
| /** |
| * To be used when {@link #isExecutedFromJar()} is true. |
| * <p/> |
| * Iterator through classes of this jar file. |
| */ |
| private static class JarClassesIterator implements Iterator<Class<?>> { |
| /** Used to go through classes in current jar */ |
| private final JarInputStream jarIn; |
| /** Are we positioned on the next class entry */ |
| private boolean entryLoaded; |
| /** Next class entry */ |
| private JarEntry currentEntry; |
| /** Folder in which to look */ |
| private final String path; |
| |
| /** |
| * @param packageName Package through which to iterate |
| * @throws IOException |
| */ |
| public JarClassesIterator(String packageName) throws IOException { |
| jarIn = new JarInputStream(new FileInputStream(new File( |
| getCurrentJar()))); |
| entryLoaded = false; |
| currentEntry = null; |
| path = packageName.replace(".", File.separator); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| loadNextEntry(); |
| return currentEntry != null; |
| } |
| |
| @Override |
| public Class<?> next() { |
| loadNextEntry(); |
| if (currentEntry == null) { |
| throw new NoSuchElementException(); |
| } |
| entryLoaded = false; |
| |
| String className = currentEntry.getName().replace(".class", |
| "").replace(File.separator, "."); |
| return loadClass(className); |
| } |
| |
| /** |
| * Sets position to next class entry |
| */ |
| private void loadNextEntry() { |
| while (!entryLoaded) { |
| try { |
| currentEntry = jarIn.getNextJarEntry(); |
| if (currentEntry == null || (currentEntry.getName().endsWith( |
| ".class") && (currentEntry.getName().startsWith(path)))) { |
| entryLoaded = true; |
| } else { |
| currentEntry = null; |
| } |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| if (currentEntry == null) { |
| try { |
| jarIn.close(); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException( |
| "Can't remove from classes iterator"); |
| } |
| } |
| |
| /** |
| * To be used when {@link #isExecutedFromJar()} is false. |
| * <p/> |
| * Iterator through classes of some package. |
| */ |
| private static class GeneralClassesIterator implements Iterator<Class<?>> { |
| /** From which position in path the package name starts */ |
| private final int stringPosition; |
| /** Recursive directory iterator */ |
| private final Iterator<File> iterator; |
| |
| /** |
| * @param packageName Package through which to iterate |
| */ |
| public GeneralClassesIterator(String packageName) { |
| String mainPath = AnnotationUtils.class.getProtectionDomain() |
| .getCodeSource().getLocation().getFile(); |
| String subPath = packageName.replace(".", File.separator); |
| File directory = new File(mainPath + subPath); |
| stringPosition = directory.getPath().length() - packageName.length(); |
| List<File> files = new ArrayList<File>(); |
| addAllClassFiles(directory, files); |
| iterator = files.iterator(); |
| } |
| |
| /** |
| * Recursively add all .class files from the directory to the list. |
| * |
| * @param directory Directory from which we are adding files |
| * @param files List we add files to |
| */ |
| @edu.umd.cs.findbugs.annotations.SuppressWarnings( |
| "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") |
| private void addAllClassFiles(File directory, List<File> files) { |
| for (File file : directory.listFiles()) { |
| if (file.isDirectory()) { |
| addAllClassFiles(file, files); |
| } else if (file.getName().endsWith(".class")) { |
| files.add(file); |
| } |
| } |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return iterator.hasNext(); |
| } |
| |
| @Override |
| public Class<?> next() { |
| String className = iterator.next().getPath().substring(stringPosition) |
| .replace(".class", "").replace(File.separator, "."); |
| return loadClass(className); |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException( |
| "Can't remove from classes iterator"); |
| } |
| } |
| |
| /** |
| * Loads the class with the specified name |
| * |
| * @param className Name of the class we are loading |
| * @return Class with the specified name |
| */ |
| private static Class<?> loadClass(String className) { |
| try { |
| return Class.forName(className); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException("Error loading class " + className, e); |
| } catch (NoClassDefFoundError e) { |
| throw new RuntimeException("Error loading class " + className, e); |
| } |
| } |
| } |