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.giraph.utils;
20  
21  import java.io.FileInputStream;
22  import java.io.IOException;
23  import java.lang.annotation.Annotation;
24  import java.io.File;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.NoSuchElementException;
29  import java.util.jar.JarEntry;
30  import java.util.jar.JarInputStream;
31  
32  /**
33   * Helper class to deal with annotations in runtime.
34   */
35  public class AnnotationUtils {
36    /** Do not instantiate. */
37    private AnnotationUtils() {
38    }
39  
40    /**
41     * Finds all classes within a package which are annotated with certain
42     * annotation.
43     *
44     * @param annotation  Annotation which we are looking for
45     * @param <T>         Annotation class
46     * @param packageName Package in which to search
47     * @return The list of annotated classes
48     */
49    public static <T extends Annotation> List<Class<?>> getAnnotatedClasses(
50        Class<T> annotation, String packageName) {
51      ArrayList<Class<?>> ret = new ArrayList<Class<?>>();
52      for (Iterator<Class<?>> it = getClassesIterator(packageName);
53           it.hasNext();) {
54        Class<?> clazz = it.next();
55        if (clazz.getAnnotation(annotation) != null) {
56          ret.add(clazz);
57        }
58      }
59      return ret;
60    }
61  
62    /**
63     * @param packageName Package through which to iterate
64     * @return Iterator through the classes of this jar file (if executed from
65     *         jar) or through the classes of org package (if .class file is
66     *         executed)
67     */
68    public static Iterator<Class<?>> getClassesIterator(String packageName) {
69      if (isExecutedFromJar()) {
70        try {
71          return new JarClassesIterator(packageName);
72        } catch (IOException e) {
73          throw new RuntimeException(e);
74        }
75      } else {
76        return new GeneralClassesIterator(packageName);
77      }
78    }
79  
80    /**
81     * @return Whether or not the code is executed from jar file
82     */
83    private static boolean isExecutedFromJar() {
84      return AnnotationUtils.class.getResource("AnnotationUtils.class")
85          .getProtocol().equals("jar");
86    }
87  
88    /**
89     * To be used when {@link #isExecutedFromJar()}  is true.
90     *
91     * @return If executed from jar file returns the path to the jar
92     *         (otherwise it will return the folder in which this .class file is
93     *         located)
94     */
95    private static String getCurrentJar() {
96      return AnnotationUtils.class.getProtectionDomain().getCodeSource()
97          .getLocation().getFile();
98    }
99  
100   /**
101    * To be used when {@link #isExecutedFromJar()}  is true.
102    * <p/>
103    * Iterator through classes of this jar file.
104    */
105   private static class JarClassesIterator implements Iterator<Class<?>> {
106     /** Used to go through classes in current jar */
107     private final JarInputStream jarIn;
108     /** Are we positioned on the next class entry */
109     private boolean entryLoaded;
110     /** Next class entry */
111     private JarEntry currentEntry;
112     /** Folder in which to look */
113     private final String path;
114 
115     /**
116      * @param packageName Package through which to iterate
117      * @throws IOException
118      */
119     public JarClassesIterator(String packageName) throws IOException {
120       jarIn = new JarInputStream(new FileInputStream(new File(
121           getCurrentJar())));
122       entryLoaded = false;
123       currentEntry = null;
124       path = packageName.replace(".", File.separator);
125     }
126 
127     @Override
128     public boolean hasNext() {
129       loadNextEntry();
130       return currentEntry != null;
131     }
132 
133     @Override
134     public Class<?> next() {
135       loadNextEntry();
136       if (currentEntry == null) {
137         throw new NoSuchElementException();
138       }
139       entryLoaded = false;
140 
141       String className = currentEntry.getName().replace(".class",
142           "").replace(File.separator, ".");
143       return loadClass(className);
144     }
145 
146     /**
147      * Sets position to next class entry
148      */
149     private void loadNextEntry() {
150       while (!entryLoaded) {
151         try {
152           currentEntry = jarIn.getNextJarEntry();
153           if (currentEntry == null || (currentEntry.getName().endsWith(
154               ".class") && (currentEntry.getName().startsWith(path)))) {
155             entryLoaded = true;
156           } else {
157             currentEntry = null;
158           }
159         } catch (IOException e) {
160           throw new RuntimeException(e);
161         }
162       }
163       if (currentEntry == null) {
164         try {
165           jarIn.close();
166         } catch (IOException e) {
167           throw new RuntimeException(e);
168         }
169       }
170     }
171 
172     @Override
173     public void remove() {
174       throw new UnsupportedOperationException(
175           "Can't remove from classes iterator");
176     }
177   }
178 
179   /**
180    * To be used when {@link #isExecutedFromJar()}  is false.
181    * <p/>
182    * Iterator through classes of some package.
183    */
184   private static class GeneralClassesIterator implements Iterator<Class<?>> {
185     /** From which position in path the package name starts */
186     private final int stringPosition;
187     /** Recursive directory iterator */
188     private final Iterator<File> iterator;
189 
190     /**
191      * @param packageName Package through which to iterate
192      */
193     public GeneralClassesIterator(String packageName) {
194       String mainPath = AnnotationUtils.class.getProtectionDomain()
195           .getCodeSource().getLocation().getFile();
196       String subPath = packageName.replace(".", File.separator);
197       File directory = new File(mainPath + subPath);
198       stringPosition = directory.getPath().length() - packageName.length();
199       List<File> files = new ArrayList<File>();
200       addAllClassFiles(directory, files);
201       iterator = files.iterator();
202     }
203 
204     /**
205      * Recursively add all .class files from the directory to the list.
206      *
207      * @param directory Directory from which we are adding files
208      * @param files List we add files to
209      */
210     private void addAllClassFiles(File directory, List<File> files) {
211       for (File file : directory.listFiles()) {
212         if (file.isDirectory()) {
213           addAllClassFiles(file, files);
214         } else if (file.getName().endsWith(".class")) {
215           files.add(file);
216         }
217       }
218     }
219 
220     @Override
221     public boolean hasNext() {
222       return iterator.hasNext();
223     }
224 
225     @Override
226     public Class<?> next() {
227       String className = iterator.next().getPath().substring(stringPosition)
228           .replace(".class", "").replace(File.separator, ".");
229       return loadClass(className);
230     }
231 
232     @Override
233     public void remove() {
234       throw new UnsupportedOperationException(
235           "Can't remove from classes iterator");
236     }
237   }
238 
239   /**
240    * Loads the class with the specified name
241    *
242    * @param className Name of the class we are loading
243    * @return Class with the specified name
244    */
245   private static Class<?> loadClass(String className) {
246     try {
247       return Class.forName(className);
248     } catch (ClassNotFoundException e) {
249       throw new RuntimeException("Error loading class " + className, e);
250     } catch (NoClassDefFoundError e) {
251       throw new RuntimeException("Error loading class " + className, e);
252     }
253   }
254 }