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}