001/* 
002 * JKNIV, utils - Helper utilities for jdk code.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.reflect;
021
022import java.io.File;
023import java.io.IOException;
024import java.net.URL;
025import java.util.Enumeration;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.Set;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import net.sf.jkniv.asserts.Assertable;
034import net.sf.jkniv.asserts.AssertsFactory;
035
036/**
037 * Adapted from https://dzone.com/articles/get-all-classes-within-package
038 *
039 *@author Alisson Gomes
040 */
041public final class Packages
042{
043    private final static Logger     LOG     = LoggerFactory.getLogger(Packages.class);
044    private final static Assertable notNull = AssertsFactory.getNotNull();
045    private String[]                packagesNames;
046    private boolean                 recursive;
047    private boolean                 evictInterface;
048    private boolean                 evictEnum;
049    private boolean                 evictAnnotation;
050    private boolean                 evictAnonymousClass;
051    private boolean                 evictClass;
052    private final Set<Class<?>>     classes;
053    private final Set<String>       resourceNames;
054    private final Set<String>       suffixResources;
055    private boolean                 scanned;
056    private final ClassLoader       classLoader;
057    
058    public Packages(String packageName)
059    {
060        this(packageName, false, DefaultClassLoader.getClassLoader());
061    }
062    
063    public Packages(String packageName, ClassLoader classLoader)
064    {
065        this(packageName, false, classLoader);
066    }
067    
068    public Packages(String packageName, boolean recursive)
069    {
070        this(packageName, recursive, DefaultClassLoader.getClassLoader());
071    }
072    
073    public Packages(String packageName, boolean recursive, ClassLoader classLoader)
074    {
075        notNull.verify(packageName, classLoader);
076        this.packagesNames = packageName.split(",");
077        this.recursive = recursive;
078        this.evictAnnotation = false;
079        this.evictAnonymousClass = false;
080        this.evictClass = false;
081        this.evictEnum = false;
082        this.evictInterface = false;
083        this.scanned = false;
084        this.classLoader = classLoader;
085        this.resourceNames = new HashSet<String>();
086        this.classes = new HashSet<Class<?>>();
087        this.suffixResources = new HashSet<String>();
088        this.suffixResources.add(".class");
089    }
090    
091    /**
092     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
093     *
094     * @return The classes
095     * @throws IOException failed when read directory resources
096     */
097    public Iterator<Class<?>> scan() throws IOException
098    {
099        Set<File> dirs = new HashSet<File>();
100        for (String pack : packagesNames)
101        {
102            Enumeration<URL> resources = classLoader.getResources(pack.replace('.', '/'));
103            
104            while (resources.hasMoreElements())
105            {
106                URL resource = resources.nextElement();
107                dirs.add(new File(resource.getFile()));
108            }
109            
110            for (File directory : dirs)
111            {
112                classes.addAll(findClasses(directory, pack));
113            }
114        }
115        this.scanned = true;
116        return classes.iterator(); //classes.toArray(new Class[classes.size()]);
117    }
118    
119    /**
120     * Scans the resource accessible from the context class loader which belong to the given package and subpackages.
121     *
122     * @return The classes
123     * @throws IOException failed when read directory resources
124     */
125    public Iterator<String> scanResource() throws IOException
126    {
127        Set<File> dirs = new HashSet<File>();
128        for (String pack : packagesNames)
129        {
130            Enumeration<URL> resources = classLoader.getResources(pack.replace('.', '/'));
131            
132            while (resources.hasMoreElements())
133            {
134                URL resource = resources.nextElement();
135                dirs.add(new File(resource.getFile()));
136            }
137            
138            for (File directory : dirs)
139            {
140                resourceNames.addAll(findResource(directory, pack));
141            }
142        }
143        this.scanned = true;
144        return resourceNames.iterator(); //classes.toArray(new Class[classes.size()]);
145    }
146    
147    public Class<?>[] asArray()
148    {
149        if (!scanned)
150            throw new ReflectionException("Package [" + packagesNames + "] doesn't scanned, call scan first");
151        
152        return classes.toArray(new Class[classes.size()]);
153    }
154    
155    public String[] asResources()
156    {
157        if (!scanned)
158            throw new ReflectionException("Package [" + packagesNames + "] doesn't scanned, call scan first");
159        
160        return resourceNames.toArray(new String[resourceNames.size()]);
161    }
162    
163    /**
164     * Recursive method used to find all classes in a given directory and subdirs.
165     *
166     * @param directory   The base directory
167     * @param packageName The package name for classes found inside the base directory
168     * @return The classes
169     */
170    private Set<Class<?>> findClasses(File directory, String packageName)
171    {
172        Set<Class<?>> classes = new HashSet<Class<?>>();
173        if (!directory.exists())
174            return classes;
175        
176        File[] files = directory.listFiles();
177        
178        for (File file : files)
179        {
180            if (file.isDirectory() && recursive)
181            {
182                assert !file.getName().contains(".");
183                classes.addAll(findClasses(file, packageName + "." + file.getName()));
184            }
185            else if (file.getName().endsWith(".class"))
186            {
187                String s = null;
188                try
189                {
190                    s = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);
191                    Class<?> c = Class.forName(s);
192                    if (!evict(c))
193                    {
194                        classes.add(c);
195                        resourceNames.add(s);
196                    }
197                }
198                catch (ClassNotFoundException e)
199                {
200                    resourceNames.add(s);
201                    LOG.warn("Cannot get [" + s + "] associated with the class or interface ");
202                }
203            }
204        }
205        return classes;
206    }
207
208    /**
209     * Recursive method used to find all classes in a given directory and subdirs.
210     *
211     * @param directory   The base directory
212     * @param packageName The package name for classes found inside the base directory
213     * @return The classes
214     */
215    private Set<String> findResource(File directory, String packageName)
216    {
217        Set<String> classes = new HashSet<String>();
218        if (!directory.exists())
219            return classes;
220        
221        File[] files = directory.listFiles();
222        
223        for (File file : files)
224        {
225            if (file.isDirectory() && recursive)
226            {
227                assert !file.getName().contains(".");
228                classes.addAll(findResource(file, packageName + "." + file.getName()));
229            }
230            else if (hasSuffix(file.getName()))
231            {
232                String s = null;
233                s = packageName + '.' + file.getName();
234                classes.add(s);
235            }
236        }
237        return classes;
238    }
239
240    public Packages evictInterfaces()
241    {
242        this.evictInterface = true;
243        return this;
244    }
245    
246    public Packages evictEnums()
247    {
248        this.evictEnum = true;
249        return this;
250    }
251    
252    public Packages evictAnnotations()
253    {
254        this.evictAnnotation = true;
255        return this;
256    }
257    
258    public Packages evictAnonymousClasses()
259    {
260        this.evictAnonymousClass = true;
261        return this;
262    }
263    
264    public Packages evictClasses()
265    {
266        this.evictClass = true;
267        return this;
268    }
269    
270    public void onlyInterfaces()
271    {
272        evictAnnotations().evictAnonymousClasses().evictEnums().evictClasses();
273    }
274    
275    public void onlyEnums()
276    {
277        evictAnnotations().evictAnonymousClasses().evictClasses().evictInterfaces();
278    }
279    
280    public void onlyAnnotations()
281    {
282        evictClasses().evictAnonymousClasses().evictEnums().evictInterfaces();
283    }
284    
285    public void onlyAnonymousClasses()
286    {
287        evictAnnotations().evictClasses().evictEnums().evictInterfaces();
288    }
289    
290    public void onlyClasses()
291    {
292        evictAnnotations().evictAnonymousClasses().evictEnums().evictInterfaces();
293    }
294
295    public void onlyResource(String suffix)
296    {
297        this.suffixResources.clear();
298        this.suffixResources.add(suffix);
299    }
300    
301    private boolean hasSuffix(String resourceName)
302    {
303        int index = resourceName.lastIndexOf(".");
304        String suffix = resourceName.substring(index, resourceName.length());
305        return this.suffixResources.contains(suffix);
306    }
307    private boolean evict(Class<?> c)
308    {
309        boolean answer = false;
310        if (c.isInterface() && evictInterface)
311            answer = true;
312        else if (c.isEnum() && evictEnum)
313            answer = true;
314        else if (c.isAnnotation() && evictAnnotation)
315            answer = true;
316        else if (c.isAnonymousClass() && evictAnonymousClass)
317            answer = true;
318        else if (evictClass && !c.isInterface() && !c.isEnum() && !c.isAnnotation() && !c.isAnonymousClass())
319            answer = true;
320        
321        return answer;
322    }
323    
324}