| 1 | /* |
|---|
| 2 | * This program is free software; you can redistribute it and/or modify |
|---|
| 3 | * it under the terms of the GNU General Public License as published by |
|---|
| 4 | * the Free Software Foundation; either version 2 of the License, or |
|---|
| 5 | * (at your option) any later version. |
|---|
| 6 | * |
|---|
| 7 | * This program is distributed in the hope that it will be useful, |
|---|
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 10 | * GNU General Public License for more details. |
|---|
| 11 | * |
|---|
| 12 | * You should have received a copy of the GNU General Public License |
|---|
| 13 | * along with this program; if not, write to the Free Software |
|---|
| 14 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
|---|
| 15 | */ |
|---|
| 16 | |
|---|
| 17 | /* |
|---|
| 18 | * ClassDiscovery.java |
|---|
| 19 | * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand |
|---|
| 20 | * |
|---|
| 21 | */ |
|---|
| 22 | |
|---|
| 23 | package weka.core; |
|---|
| 24 | |
|---|
| 25 | import java.io.File; |
|---|
| 26 | import java.lang.reflect.Modifier; |
|---|
| 27 | import java.net.URISyntaxException; |
|---|
| 28 | import java.net.URL; |
|---|
| 29 | import java.net.URLClassLoader; |
|---|
| 30 | import java.util.Collections; |
|---|
| 31 | import java.util.Comparator; |
|---|
| 32 | import java.util.Enumeration; |
|---|
| 33 | import java.util.HashSet; |
|---|
| 34 | import java.util.Hashtable; |
|---|
| 35 | import java.util.StringTokenizer; |
|---|
| 36 | import java.util.Vector; |
|---|
| 37 | import java.util.jar.JarEntry; |
|---|
| 38 | import java.util.jar.JarFile; |
|---|
| 39 | |
|---|
| 40 | /** |
|---|
| 41 | * This class is used for discovering classes that implement a certain |
|---|
| 42 | * interface or a derived from a certain class. |
|---|
| 43 | * |
|---|
| 44 | * @author FracPete (fracpete at waikato dot ac dot nz) |
|---|
| 45 | * @version $Revision: 6074 $ |
|---|
| 46 | * @see StringCompare |
|---|
| 47 | */ |
|---|
| 48 | public class ClassDiscovery |
|---|
| 49 | implements RevisionHandler { |
|---|
| 50 | |
|---|
| 51 | /** whether to output some debug information. */ |
|---|
| 52 | public final static boolean VERBOSE = false; |
|---|
| 53 | |
|---|
| 54 | /** for caching queries (classname+packagename <-> Vector with classnames). */ |
|---|
| 55 | protected static Hashtable<String,Vector<String>> m_Cache; |
|---|
| 56 | |
|---|
| 57 | /** notify if VERBOSE is still on */ |
|---|
| 58 | static { |
|---|
| 59 | if (VERBOSE) |
|---|
| 60 | System.err.println(ClassDiscovery.class.getName() + ": VERBOSE ON"); |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | /** |
|---|
| 64 | * Checks whether the "otherclass" is a subclass of the given "superclass". |
|---|
| 65 | * |
|---|
| 66 | * @param superclass the superclass to check against |
|---|
| 67 | * @param otherclass this class is checked whether it is a subclass |
|---|
| 68 | * of the the superclass |
|---|
| 69 | * @return TRUE if "otherclass" is a true subclass |
|---|
| 70 | */ |
|---|
| 71 | public static boolean isSubclass(String superclass, String otherclass) { |
|---|
| 72 | try { |
|---|
| 73 | return isSubclass(Class.forName(superclass), Class.forName(otherclass)); |
|---|
| 74 | } |
|---|
| 75 | catch (Exception e) { |
|---|
| 76 | return false; |
|---|
| 77 | } |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * Checks whether the "otherclass" is a subclass of the given "superclass". |
|---|
| 82 | * |
|---|
| 83 | * @param superclass the superclass to check against |
|---|
| 84 | * @param otherclass this class is checked whether it is a subclass |
|---|
| 85 | * of the the superclass |
|---|
| 86 | * @return TRUE if "otherclass" is a true subclass |
|---|
| 87 | */ |
|---|
| 88 | public static boolean isSubclass(Class superclass, Class otherclass) { |
|---|
| 89 | Class currentclass; |
|---|
| 90 | boolean result; |
|---|
| 91 | |
|---|
| 92 | result = false; |
|---|
| 93 | currentclass = otherclass; |
|---|
| 94 | do { |
|---|
| 95 | result = currentclass.equals(superclass); |
|---|
| 96 | |
|---|
| 97 | // topmost class reached? |
|---|
| 98 | if (currentclass.equals(Object.class)) |
|---|
| 99 | break; |
|---|
| 100 | |
|---|
| 101 | if (!result) |
|---|
| 102 | currentclass = currentclass.getSuperclass(); |
|---|
| 103 | } |
|---|
| 104 | while (!result); |
|---|
| 105 | |
|---|
| 106 | return result; |
|---|
| 107 | } |
|---|
| 108 | |
|---|
| 109 | /** |
|---|
| 110 | * Checks whether the given class implements the given interface. |
|---|
| 111 | * |
|---|
| 112 | * @param intf the interface to look for in the given class |
|---|
| 113 | * @param cls the class to check for the interface |
|---|
| 114 | * @return TRUE if the class contains the interface |
|---|
| 115 | */ |
|---|
| 116 | public static boolean hasInterface(String intf, String cls) { |
|---|
| 117 | try { |
|---|
| 118 | return hasInterface(Class.forName(intf), Class.forName(cls)); |
|---|
| 119 | } |
|---|
| 120 | catch (Exception e) { |
|---|
| 121 | return false; |
|---|
| 122 | } |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | /** |
|---|
| 126 | * Checks whether the given class implements the given interface. |
|---|
| 127 | * |
|---|
| 128 | * @param intf the interface to look for in the given class |
|---|
| 129 | * @param cls the class to check for the interface |
|---|
| 130 | * @return TRUE if the class contains the interface |
|---|
| 131 | */ |
|---|
| 132 | public static boolean hasInterface(Class intf, Class cls) { |
|---|
| 133 | Class[] intfs; |
|---|
| 134 | int i; |
|---|
| 135 | boolean result; |
|---|
| 136 | Class currentclass; |
|---|
| 137 | |
|---|
| 138 | result = false; |
|---|
| 139 | currentclass = cls; |
|---|
| 140 | do { |
|---|
| 141 | // check all the interfaces, this class implements |
|---|
| 142 | intfs = currentclass.getInterfaces(); |
|---|
| 143 | for (i = 0; i < intfs.length; i++) { |
|---|
| 144 | if (intfs[i].equals(intf)) { |
|---|
| 145 | result = true; |
|---|
| 146 | break; |
|---|
| 147 | } |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | // get parent class |
|---|
| 151 | if (!result) { |
|---|
| 152 | currentclass = currentclass.getSuperclass(); |
|---|
| 153 | |
|---|
| 154 | // topmost class reached or no superclass? |
|---|
| 155 | if ( (currentclass == null) || (currentclass.equals(Object.class)) ) |
|---|
| 156 | break; |
|---|
| 157 | } |
|---|
| 158 | } |
|---|
| 159 | while (!result); |
|---|
| 160 | |
|---|
| 161 | return result; |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | /** |
|---|
| 165 | * If the given package can be found in this part of the classpath then |
|---|
| 166 | * an URL object is returned, otherwise <code>null</code>. |
|---|
| 167 | * |
|---|
| 168 | * @param classpathPart the part of the classpath to look for the package |
|---|
| 169 | * @param pkgname the package to look for |
|---|
| 170 | * @return if found, the url as string, otherwise null |
|---|
| 171 | */ |
|---|
| 172 | protected static URL getURL(String classpathPart, String pkgname) { |
|---|
| 173 | String urlStr; |
|---|
| 174 | URL result; |
|---|
| 175 | File classpathFile; |
|---|
| 176 | File file; |
|---|
| 177 | JarFile jarfile; |
|---|
| 178 | Enumeration enm; |
|---|
| 179 | String pkgnameTmp; |
|---|
| 180 | |
|---|
| 181 | result = null; |
|---|
| 182 | urlStr = null; |
|---|
| 183 | |
|---|
| 184 | try { |
|---|
| 185 | classpathFile = new File(classpathPart); |
|---|
| 186 | |
|---|
| 187 | // directory or jar? |
|---|
| 188 | if (classpathFile.isDirectory()) { |
|---|
| 189 | // does the package exist in this directory? |
|---|
| 190 | file = new File(classpathPart + pkgname); |
|---|
| 191 | if (file.exists()) |
|---|
| 192 | urlStr = "file:" + classpathPart + pkgname; |
|---|
| 193 | } |
|---|
| 194 | else { |
|---|
| 195 | // is package actually included in jar? |
|---|
| 196 | jarfile = new JarFile(classpathPart); |
|---|
| 197 | enm = jarfile.entries(); |
|---|
| 198 | pkgnameTmp = pkgname.substring(1); // remove the leading "/" |
|---|
| 199 | while (enm.hasMoreElements()) { |
|---|
| 200 | if (enm.nextElement().toString().startsWith(pkgnameTmp)) { |
|---|
| 201 | urlStr = "jar:file:" + classpathPart + "!" + pkgname; |
|---|
| 202 | break; |
|---|
| 203 | } |
|---|
| 204 | } |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | catch (Exception e) { |
|---|
| 208 | // ignore |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | // try to generate URL from url string |
|---|
| 212 | if (urlStr != null) { |
|---|
| 213 | try { |
|---|
| 214 | result = new URL(urlStr); |
|---|
| 215 | } |
|---|
| 216 | catch (Exception e) { |
|---|
| 217 | System.err.println( |
|---|
| 218 | "Trying to create URL from '" + urlStr |
|---|
| 219 | + "' generates this exception:\n" + e); |
|---|
| 220 | result = null; |
|---|
| 221 | } |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | return result; |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | /** |
|---|
| 228 | * Checks the given packages for classes that inherited from the given class, |
|---|
| 229 | * in case it's a class, or implement this class, in case it's an interface. |
|---|
| 230 | * |
|---|
| 231 | * @param classname the class/interface to look for |
|---|
| 232 | * @param pkgnames the packages to search in |
|---|
| 233 | * @return a list with all the found classnames |
|---|
| 234 | */ |
|---|
| 235 | public static Vector<String> find(String classname, String[] pkgnames) { |
|---|
| 236 | Vector<String> result; |
|---|
| 237 | Class cls; |
|---|
| 238 | |
|---|
| 239 | result = new Vector<String>(); |
|---|
| 240 | |
|---|
| 241 | try { |
|---|
| 242 | cls = Class.forName(classname); |
|---|
| 243 | result = find(cls, pkgnames); |
|---|
| 244 | } |
|---|
| 245 | catch (Exception e) { |
|---|
| 246 | e.printStackTrace(); |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | return result; |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | /** |
|---|
| 253 | * Checks the given package for classes that inherited from the given class, |
|---|
| 254 | * in case it's a class, or implement this class, in case it's an interface. |
|---|
| 255 | * |
|---|
| 256 | * @param classname the class/interface to look for |
|---|
| 257 | * @param pkgname the package to search in |
|---|
| 258 | * @return a list with all the found classnames |
|---|
| 259 | */ |
|---|
| 260 | public static Vector<String> find(String classname, String pkgname) { |
|---|
| 261 | Vector<String> result; |
|---|
| 262 | Class cls; |
|---|
| 263 | |
|---|
| 264 | result = new Vector<String>(); |
|---|
| 265 | |
|---|
| 266 | try { |
|---|
| 267 | cls = Class.forName(classname); |
|---|
| 268 | result = find(cls, pkgname); |
|---|
| 269 | } |
|---|
| 270 | catch (Exception e) { |
|---|
| 271 | e.printStackTrace(); |
|---|
| 272 | } |
|---|
| 273 | |
|---|
| 274 | return result; |
|---|
| 275 | } |
|---|
| 276 | |
|---|
| 277 | /** |
|---|
| 278 | * Checks the given packages for classes that inherited from the given class, |
|---|
| 279 | * in case it's a class, or implement this class, in case it's an interface. |
|---|
| 280 | * |
|---|
| 281 | * @param cls the class/interface to look for |
|---|
| 282 | * @param pkgnames the packages to search in |
|---|
| 283 | * @return a list with all the found classnames |
|---|
| 284 | */ |
|---|
| 285 | public static Vector<String> find(Class cls, String[] pkgnames) { |
|---|
| 286 | Vector<String> result; |
|---|
| 287 | int i; |
|---|
| 288 | HashSet<String> names; |
|---|
| 289 | |
|---|
| 290 | result = new Vector<String>(); |
|---|
| 291 | |
|---|
| 292 | names = new HashSet<String>(); |
|---|
| 293 | for (i = 0; i < pkgnames.length; i++) |
|---|
| 294 | names.addAll(find(cls, pkgnames[i])); |
|---|
| 295 | |
|---|
| 296 | // sort result |
|---|
| 297 | result.addAll(names); |
|---|
| 298 | Collections.sort(result, new StringCompare()); |
|---|
| 299 | |
|---|
| 300 | return result; |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | /** |
|---|
| 304 | * Checks the given package for classes that inherited from the given class, |
|---|
| 305 | * in case it's a class, or implement this class, in case it's an interface. |
|---|
| 306 | * |
|---|
| 307 | * @param cls the class/interface to look for |
|---|
| 308 | * @param pkgname the package to search in |
|---|
| 309 | * @return a list with all the found classnames |
|---|
| 310 | */ |
|---|
| 311 | public static Vector<String> find(Class cls, String pkgname) { |
|---|
| 312 | Vector<String> result; |
|---|
| 313 | StringTokenizer tok; |
|---|
| 314 | String part; |
|---|
| 315 | String pkgpath; |
|---|
| 316 | File dir; |
|---|
| 317 | File[] files; |
|---|
| 318 | URL url; |
|---|
| 319 | int i; |
|---|
| 320 | Class clsNew; |
|---|
| 321 | String classname; |
|---|
| 322 | JarFile jar; |
|---|
| 323 | JarEntry entry; |
|---|
| 324 | Enumeration enm; |
|---|
| 325 | |
|---|
| 326 | |
|---|
| 327 | // already cached? |
|---|
| 328 | result = getCache(cls, pkgname); |
|---|
| 329 | |
|---|
| 330 | if (result == null) { |
|---|
| 331 | result = new Vector<String>(); |
|---|
| 332 | |
|---|
| 333 | if (VERBOSE) |
|---|
| 334 | System.out.println( |
|---|
| 335 | "Searching for '" + cls.getName() + "' in '" + pkgname + "':"); |
|---|
| 336 | |
|---|
| 337 | // turn package into path |
|---|
| 338 | pkgpath = pkgname.replaceAll("\\.", "/"); |
|---|
| 339 | |
|---|
| 340 | // check all parts of the classpath, to include additional classes from |
|---|
| 341 | // "parallel" directories/jars, not just the first occurence |
|---|
| 342 | /*tok = new StringTokenizer( |
|---|
| 343 | System.getProperty("java.class.path"), |
|---|
| 344 | System.getProperty("path.separator")); */ |
|---|
| 345 | |
|---|
| 346 | ClassloaderUtil clu = new ClassloaderUtil(); |
|---|
| 347 | URLClassLoader sysLoader = (URLClassLoader)clu.getClass().getClassLoader(); |
|---|
| 348 | URL[] cl_urls = sysLoader.getURLs(); |
|---|
| 349 | |
|---|
| 350 | //while (tok.hasMoreTokens()) { |
|---|
| 351 | for (i = 0; i < cl_urls.length; i++) { |
|---|
| 352 | //part = tok.nextToken(); |
|---|
| 353 | part = cl_urls[i].toString(); |
|---|
| 354 | if (part.startsWith("file:")) { |
|---|
| 355 | part.replace(" ", "%20"); |
|---|
| 356 | try { |
|---|
| 357 | File temp = new File(new java.net.URI(part)); |
|---|
| 358 | part = temp.getAbsolutePath(); |
|---|
| 359 | } catch (URISyntaxException e) { |
|---|
| 360 | e.printStackTrace(); |
|---|
| 361 | } |
|---|
| 362 | } |
|---|
| 363 | if (VERBOSE) |
|---|
| 364 | System.out.println("Classpath-part: " + part); |
|---|
| 365 | |
|---|
| 366 | // does package exist in this part of the classpath? |
|---|
| 367 | url = getURL(part, "/" + pkgpath); |
|---|
| 368 | if (VERBOSE) { |
|---|
| 369 | if (url == null) |
|---|
| 370 | System.out.println(" " + pkgpath + " NOT FOUND"); |
|---|
| 371 | else |
|---|
| 372 | System.out.println(" " + pkgpath + " FOUND"); |
|---|
| 373 | } |
|---|
| 374 | if (url == null) |
|---|
| 375 | continue; |
|---|
| 376 | |
|---|
| 377 | // find classes |
|---|
| 378 | dir = new File(part + "/" + pkgpath); |
|---|
| 379 | if (dir.exists()) { |
|---|
| 380 | files = dir.listFiles(); |
|---|
| 381 | for (int j = 0; j < files.length; j++) { |
|---|
| 382 | // only class files |
|---|
| 383 | if ( (!files[j].isFile()) |
|---|
| 384 | || (!files[j].getName().endsWith(".class")) ) |
|---|
| 385 | continue; |
|---|
| 386 | |
|---|
| 387 | try { |
|---|
| 388 | classname = pkgname + "." |
|---|
| 389 | + files[j].getName().replaceAll(".*/", "") |
|---|
| 390 | .replaceAll("\\.class", ""); |
|---|
| 391 | result.add(classname); |
|---|
| 392 | } |
|---|
| 393 | catch (Exception e) { |
|---|
| 394 | e.printStackTrace(); |
|---|
| 395 | } |
|---|
| 396 | } |
|---|
| 397 | } |
|---|
| 398 | else { |
|---|
| 399 | try { |
|---|
| 400 | jar = new JarFile(part); |
|---|
| 401 | enm = jar.entries(); |
|---|
| 402 | while (enm.hasMoreElements()) { |
|---|
| 403 | entry = (JarEntry) enm.nextElement(); |
|---|
| 404 | |
|---|
| 405 | // only class files |
|---|
| 406 | if ( (entry.isDirectory()) |
|---|
| 407 | || (!entry.getName().endsWith(".class")) ) |
|---|
| 408 | continue; |
|---|
| 409 | |
|---|
| 410 | classname = entry.getName().replaceAll("\\.class", ""); |
|---|
| 411 | |
|---|
| 412 | // only classes in the particular package |
|---|
| 413 | if (!classname.startsWith(pkgpath)) |
|---|
| 414 | continue; |
|---|
| 415 | |
|---|
| 416 | // no sub-package |
|---|
| 417 | if (classname.substring(pkgpath.length() + 1).indexOf("/") > -1) |
|---|
| 418 | continue; |
|---|
| 419 | |
|---|
| 420 | result.add(classname.replaceAll("/", ".")); |
|---|
| 421 | } |
|---|
| 422 | } |
|---|
| 423 | catch (Exception e) { |
|---|
| 424 | e.printStackTrace(); |
|---|
| 425 | } |
|---|
| 426 | } |
|---|
| 427 | } |
|---|
| 428 | |
|---|
| 429 | // check classes |
|---|
| 430 | i = 0; |
|---|
| 431 | while (i < result.size()) { |
|---|
| 432 | try { |
|---|
| 433 | clsNew = Class.forName((String) result.get(i)); |
|---|
| 434 | |
|---|
| 435 | // no abstract classes |
|---|
| 436 | if (Modifier.isAbstract(clsNew.getModifiers())) |
|---|
| 437 | result.remove(i); |
|---|
| 438 | // must implement interface |
|---|
| 439 | else if ( (cls.isInterface()) && (!hasInterface(cls, clsNew)) ) |
|---|
| 440 | result.remove(i); |
|---|
| 441 | // must be derived from class |
|---|
| 442 | else if ( (!cls.isInterface()) && (!isSubclass(cls, clsNew)) ) |
|---|
| 443 | result.remove(i); |
|---|
| 444 | else |
|---|
| 445 | i++; |
|---|
| 446 | } |
|---|
| 447 | catch (Throwable e) { |
|---|
| 448 | System.err.println("Checking class: " + result.get(i)); |
|---|
| 449 | e.printStackTrace(); |
|---|
| 450 | result.remove(i); |
|---|
| 451 | } |
|---|
| 452 | } |
|---|
| 453 | |
|---|
| 454 | // sort result |
|---|
| 455 | Collections.sort(result, new StringCompare()); |
|---|
| 456 | |
|---|
| 457 | // add to cache |
|---|
| 458 | addCache(cls, pkgname, result); |
|---|
| 459 | } |
|---|
| 460 | |
|---|
| 461 | return result; |
|---|
| 462 | } |
|---|
| 463 | |
|---|
| 464 | /** |
|---|
| 465 | * adds all the sub-directories recursively to the list. |
|---|
| 466 | * |
|---|
| 467 | * @param prefix the path prefix |
|---|
| 468 | * @param dir the directory to look in for sub-dirs |
|---|
| 469 | * @param list the current list of sub-dirs |
|---|
| 470 | * @return the new list of sub-dirs |
|---|
| 471 | */ |
|---|
| 472 | protected static HashSet<String> getSubDirectories(String prefix, File dir, HashSet<String> list) { |
|---|
| 473 | File[] files; |
|---|
| 474 | int i; |
|---|
| 475 | String newPrefix; |
|---|
| 476 | |
|---|
| 477 | // add directory to the list |
|---|
| 478 | if (prefix == null) |
|---|
| 479 | newPrefix = ""; |
|---|
| 480 | else if (prefix.length() == 0) |
|---|
| 481 | newPrefix = dir.getName(); |
|---|
| 482 | else |
|---|
| 483 | newPrefix = prefix + "." + dir.getName(); |
|---|
| 484 | |
|---|
| 485 | if (newPrefix.length() != 0) |
|---|
| 486 | list.add(newPrefix); |
|---|
| 487 | |
|---|
| 488 | // search for sub-directories |
|---|
| 489 | files = dir.listFiles(); |
|---|
| 490 | if (files != null) { |
|---|
| 491 | for (i = 0; i < files.length; i++) { |
|---|
| 492 | if (files[i].isDirectory()) |
|---|
| 493 | list = getSubDirectories(newPrefix, files[i], list); |
|---|
| 494 | } |
|---|
| 495 | } |
|---|
| 496 | |
|---|
| 497 | return list; |
|---|
| 498 | } |
|---|
| 499 | |
|---|
| 500 | /** |
|---|
| 501 | * Lists all packages it can find in the classpath. |
|---|
| 502 | * |
|---|
| 503 | * @return a list with all the found packages |
|---|
| 504 | */ |
|---|
| 505 | public static Vector<String> findPackages() { |
|---|
| 506 | Vector<String> result; |
|---|
| 507 | StringTokenizer tok; |
|---|
| 508 | String part; |
|---|
| 509 | File file; |
|---|
| 510 | JarFile jar; |
|---|
| 511 | JarEntry entry; |
|---|
| 512 | Enumeration<JarEntry> enm; |
|---|
| 513 | HashSet<String> set; |
|---|
| 514 | |
|---|
| 515 | result = new Vector<String>(); |
|---|
| 516 | set = new HashSet<String>(); |
|---|
| 517 | |
|---|
| 518 | // check all parts of the classpath, to include additional classes from |
|---|
| 519 | // "parallel" directories/jars, not just the first occurence |
|---|
| 520 | tok = new StringTokenizer( |
|---|
| 521 | System.getProperty("java.class.path"), |
|---|
| 522 | System.getProperty("path.separator")); |
|---|
| 523 | |
|---|
| 524 | while (tok.hasMoreTokens()) { |
|---|
| 525 | part = tok.nextToken(); |
|---|
| 526 | if (VERBOSE) |
|---|
| 527 | System.out.println("Classpath-part: " + part); |
|---|
| 528 | |
|---|
| 529 | // find classes |
|---|
| 530 | file = new File(part); |
|---|
| 531 | if (file.isDirectory()) { |
|---|
| 532 | set = getSubDirectories(null, file, set); |
|---|
| 533 | } |
|---|
| 534 | else if (file.exists()) { |
|---|
| 535 | try { |
|---|
| 536 | jar = new JarFile(part); |
|---|
| 537 | enm = jar.entries(); |
|---|
| 538 | while (enm.hasMoreElements()) { |
|---|
| 539 | entry = (JarEntry) enm.nextElement(); |
|---|
| 540 | |
|---|
| 541 | // only directories |
|---|
| 542 | if (entry.isDirectory()) |
|---|
| 543 | set.add(entry.getName().replaceAll("/", ".").replaceAll("\\.$", "")); |
|---|
| 544 | } |
|---|
| 545 | } |
|---|
| 546 | catch (Exception e) { |
|---|
| 547 | e.printStackTrace(); |
|---|
| 548 | } |
|---|
| 549 | } |
|---|
| 550 | } |
|---|
| 551 | |
|---|
| 552 | // sort result |
|---|
| 553 | set.remove("META-INF"); |
|---|
| 554 | result.addAll(set); |
|---|
| 555 | Collections.sort(result, new StringCompare()); |
|---|
| 556 | |
|---|
| 557 | return result; |
|---|
| 558 | } |
|---|
| 559 | |
|---|
| 560 | /** |
|---|
| 561 | * initializes the cache for the classnames. |
|---|
| 562 | */ |
|---|
| 563 | protected static void initCache() { |
|---|
| 564 | if (m_Cache == null) |
|---|
| 565 | m_Cache = new Hashtable<String,Vector<String>>(); |
|---|
| 566 | } |
|---|
| 567 | |
|---|
| 568 | /** |
|---|
| 569 | * adds the list of classnames to the cache. |
|---|
| 570 | * |
|---|
| 571 | * @param cls the class to cache the classnames for |
|---|
| 572 | * @param pkgname the package name the classes were found in |
|---|
| 573 | * @param classnames the list of classnames to cache |
|---|
| 574 | */ |
|---|
| 575 | protected static void addCache(Class cls, String pkgname, Vector<String> classnames) { |
|---|
| 576 | initCache(); |
|---|
| 577 | m_Cache.put(cls.getName() + "-" + pkgname, classnames); |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | /** |
|---|
| 581 | * returns the list of classnames associated with this class and package, if |
|---|
| 582 | * available, otherwise null. |
|---|
| 583 | * |
|---|
| 584 | * @param cls the class to get the classnames for |
|---|
| 585 | * @param pkgname the package name for the classes |
|---|
| 586 | * @return the classnames if found, otherwise null |
|---|
| 587 | */ |
|---|
| 588 | protected static Vector<String> getCache(Class cls, String pkgname) { |
|---|
| 589 | initCache(); |
|---|
| 590 | return m_Cache.get(cls.getName() + "-" + pkgname); |
|---|
| 591 | } |
|---|
| 592 | |
|---|
| 593 | /** |
|---|
| 594 | * clears the cache for class/classnames relation. |
|---|
| 595 | */ |
|---|
| 596 | public static void clearCache() { |
|---|
| 597 | initCache(); |
|---|
| 598 | m_Cache.clear(); |
|---|
| 599 | } |
|---|
| 600 | |
|---|
| 601 | /** |
|---|
| 602 | * Returns the revision string. |
|---|
| 603 | * |
|---|
| 604 | * @return the revision |
|---|
| 605 | */ |
|---|
| 606 | public String getRevision() { |
|---|
| 607 | return RevisionUtils.extract("$Revision: 6074 $"); |
|---|
| 608 | } |
|---|
| 609 | |
|---|
| 610 | /** |
|---|
| 611 | * Possible calls: |
|---|
| 612 | * <ul> |
|---|
| 613 | * <li> |
|---|
| 614 | * weka.core.ClassDiscovery <packages><br/> |
|---|
| 615 | * Prints all the packages in the current classpath |
|---|
| 616 | * </li> |
|---|
| 617 | * <li> |
|---|
| 618 | * weka.core.ClassDiscovery <classname> <packagename(s)><br/> |
|---|
| 619 | * Prints the classes it found. |
|---|
| 620 | * </li> |
|---|
| 621 | * </ul> |
|---|
| 622 | * |
|---|
| 623 | * @param args the commandline arguments |
|---|
| 624 | */ |
|---|
| 625 | public static void main(String[] args) { |
|---|
| 626 | Vector<String> list; |
|---|
| 627 | Vector<String> packages; |
|---|
| 628 | int i; |
|---|
| 629 | StringTokenizer tok; |
|---|
| 630 | |
|---|
| 631 | if ((args.length == 1) && (args[0].equals("packages"))) { |
|---|
| 632 | list = findPackages(); |
|---|
| 633 | for (i = 0; i < list.size(); i++) |
|---|
| 634 | System.out.println(list.get(i)); |
|---|
| 635 | } |
|---|
| 636 | else if (args.length == 2) { |
|---|
| 637 | // packages |
|---|
| 638 | packages = new Vector<String>(); |
|---|
| 639 | tok = new StringTokenizer(args[1], ","); |
|---|
| 640 | while (tok.hasMoreTokens()) |
|---|
| 641 | packages.add(tok.nextToken()); |
|---|
| 642 | |
|---|
| 643 | // search |
|---|
| 644 | list = ClassDiscovery.find( |
|---|
| 645 | args[0], |
|---|
| 646 | (String[]) packages.toArray(new String[packages.size()])); |
|---|
| 647 | |
|---|
| 648 | // print result, if any |
|---|
| 649 | System.out.println( |
|---|
| 650 | "Searching for '" + args[0] + "' in '" + args[1] + "':\n" |
|---|
| 651 | + " " + list.size() + " found."); |
|---|
| 652 | for (i = 0; i < list.size(); i++) |
|---|
| 653 | System.out.println(" " + (i+1) + ". " + list.get(i)); |
|---|
| 654 | } |
|---|
| 655 | else { |
|---|
| 656 | System.out.println("\nUsage:"); |
|---|
| 657 | System.out.println( |
|---|
| 658 | ClassDiscovery.class.getName() + " packages"); |
|---|
| 659 | System.out.println("\tlists all packages in the classpath"); |
|---|
| 660 | System.out.println( |
|---|
| 661 | ClassDiscovery.class.getName() + " <classname> <packagename(s)>"); |
|---|
| 662 | System.out.println("\tlists classes derived from/implementing 'classname' that"); |
|---|
| 663 | System.out.println("\tcan be found in 'packagename(s)' (comma-separated list"); |
|---|
| 664 | System.out.println(); |
|---|
| 665 | System.exit(1); |
|---|
| 666 | } |
|---|
| 667 | } |
|---|
| 668 | |
|---|
| 669 | /** |
|---|
| 670 | * compares two strings. The following order is used:<br/> |
|---|
| 671 | * <ul> |
|---|
| 672 | * <li>case insensitive</li> |
|---|
| 673 | * <li>german umlauts (ä , ö etc.) or other non-ASCII letters |
|---|
| 674 | * are treated as special chars</li> |
|---|
| 675 | * <li>special chars < numbers < letters</li> |
|---|
| 676 | * </ul> |
|---|
| 677 | */ |
|---|
| 678 | public static class StringCompare |
|---|
| 679 | implements Comparator, RevisionHandler { |
|---|
| 680 | |
|---|
| 681 | /** |
|---|
| 682 | * appends blanks to the string if its shorter than <code>len</code>. |
|---|
| 683 | * |
|---|
| 684 | * @param s the string to pad |
|---|
| 685 | * @param len the minimum length for the string to have |
|---|
| 686 | * @return the padded string |
|---|
| 687 | */ |
|---|
| 688 | private String fillUp(String s, int len) { |
|---|
| 689 | while (s.length() < len) |
|---|
| 690 | s += " "; |
|---|
| 691 | return s; |
|---|
| 692 | } |
|---|
| 693 | |
|---|
| 694 | /** |
|---|
| 695 | * returns the group of the character: 0=special char, 1=number, 2=letter. |
|---|
| 696 | * |
|---|
| 697 | * @param c the character to check |
|---|
| 698 | * @return the group |
|---|
| 699 | */ |
|---|
| 700 | private int charGroup(char c) { |
|---|
| 701 | int result; |
|---|
| 702 | |
|---|
| 703 | result = 0; |
|---|
| 704 | |
|---|
| 705 | if ( (c >= 'a') && (c <= 'z') ) |
|---|
| 706 | result = 2; |
|---|
| 707 | else if ( (c >= '0') && (c <= '9') ) |
|---|
| 708 | result = 1; |
|---|
| 709 | |
|---|
| 710 | return result; |
|---|
| 711 | } |
|---|
| 712 | |
|---|
| 713 | /** |
|---|
| 714 | * Compares its two arguments for order. |
|---|
| 715 | * |
|---|
| 716 | * @param o1 the first object |
|---|
| 717 | * @param o2 the second object |
|---|
| 718 | * @return -1 if o1<o2, 0 if o1=o2 and 1 if o1&;gt;o2 |
|---|
| 719 | */ |
|---|
| 720 | public int compare(Object o1, Object o2) { |
|---|
| 721 | String s1; |
|---|
| 722 | String s2; |
|---|
| 723 | int i; |
|---|
| 724 | int result; |
|---|
| 725 | int v1; |
|---|
| 726 | int v2; |
|---|
| 727 | |
|---|
| 728 | result = 0; // they're equal |
|---|
| 729 | |
|---|
| 730 | // get lower case string |
|---|
| 731 | s1 = o1.toString().toLowerCase(); |
|---|
| 732 | s2 = o2.toString().toLowerCase(); |
|---|
| 733 | |
|---|
| 734 | // same length |
|---|
| 735 | s1 = fillUp(s1, s2.length()); |
|---|
| 736 | s2 = fillUp(s2, s1.length()); |
|---|
| 737 | |
|---|
| 738 | for (i = 0; i < s1.length(); i++) { |
|---|
| 739 | // same char? |
|---|
| 740 | if (s1.charAt(i) == s2.charAt(i)) { |
|---|
| 741 | result = 0; |
|---|
| 742 | } |
|---|
| 743 | else { |
|---|
| 744 | v1 = charGroup(s1.charAt(i)); |
|---|
| 745 | v2 = charGroup(s2.charAt(i)); |
|---|
| 746 | |
|---|
| 747 | // different type (special, number, letter)? |
|---|
| 748 | if (v1 != v2) { |
|---|
| 749 | if (v1 < v2) |
|---|
| 750 | result = -1; |
|---|
| 751 | else |
|---|
| 752 | result = 1; |
|---|
| 753 | } |
|---|
| 754 | else { |
|---|
| 755 | if (s1.charAt(i) < s2.charAt(i)) |
|---|
| 756 | result = -1; |
|---|
| 757 | else |
|---|
| 758 | result = 1; |
|---|
| 759 | } |
|---|
| 760 | |
|---|
| 761 | break; |
|---|
| 762 | } |
|---|
| 763 | } |
|---|
| 764 | |
|---|
| 765 | return result; |
|---|
| 766 | } |
|---|
| 767 | |
|---|
| 768 | /** |
|---|
| 769 | * Indicates whether some other object is "equal to" this Comparator. |
|---|
| 770 | * |
|---|
| 771 | * @param obj the object to compare with this Comparator |
|---|
| 772 | * @return true if the object is a StringCompare object as well |
|---|
| 773 | */ |
|---|
| 774 | public boolean equals(Object obj) { |
|---|
| 775 | return (obj instanceof StringCompare); |
|---|
| 776 | } |
|---|
| 777 | |
|---|
| 778 | /** |
|---|
| 779 | * Returns the revision string. |
|---|
| 780 | * |
|---|
| 781 | * @return the revision |
|---|
| 782 | */ |
|---|
| 783 | public String getRevision() { |
|---|
| 784 | return RevisionUtils.extract("$Revision: 6074 $"); |
|---|
| 785 | } |
|---|
| 786 | } |
|---|
| 787 | } |
|---|