1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.bcel.util; 19 20 import java.io.DataInputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FilenameFilter; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.net.MalformedURLException; 27 import java.net.URL; 28 import java.util.ArrayList; 29 import java.util.Enumeration; 30 import java.util.List; 31 import java.util.Locale; 32 import java.util.StringTokenizer; 33 import java.util.Vector; 34 import java.util.zip.ZipEntry; 35 import java.util.zip.ZipFile; 36 37 /** 38 * Responsible for loading (class) files from the CLASSPATH. Inspired by 39 * sun.tools.ClassPath. 40 * 41 * @version $Id$ 42 */ 43 public class ClassPath { 44 45 public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath(getClassPath()); 46 47 private static final FilenameFilter ARCHIVE_FILTER = new FilenameFilter() { 48 49 @Override 50 public boolean accept( final File dir, String name ) { 51 name = name.toLowerCase(Locale.ENGLISH); 52 return name.endsWith(".zip") || name.endsWith(".jar"); 53 } 54 }; 55 56 private final PathEntry[] paths; 57 private final String class_path; 58 private ClassPath parent; 59 ClassPath(final ClassPath parent, final String class_path)60 public ClassPath(final ClassPath parent, final String class_path) { 61 this(class_path); 62 this.parent = parent; 63 } 64 65 /** 66 * Search for classes in given path. 67 * 68 * @param class_path 69 */ ClassPath(final String class_path)70 public ClassPath(final String class_path) { 71 this.class_path = class_path; 72 final List<PathEntry> list = new ArrayList<>(); 73 for (final StringTokenizer tok = new StringTokenizer(class_path, File.pathSeparator); tok.hasMoreTokens();) { 74 final String path = tok.nextToken(); 75 if (!path.isEmpty()) { 76 final File file = new File(path); 77 try { 78 if (file.exists()) { 79 if (file.isDirectory()) { 80 list.add(new Dir(path)); 81 } else { 82 list.add(new Zip(new ZipFile(file))); 83 } 84 } 85 } catch (final IOException e) { 86 if (path.endsWith(".zip") || path.endsWith(".jar")) { 87 System.err.println("CLASSPATH component " + file + ": " + e); 88 } 89 } 90 } 91 } 92 paths = new PathEntry[list.size()]; 93 list.toArray(paths); 94 } 95 96 /** 97 * Search for classes in CLASSPATH. 98 * @deprecated Use SYSTEM_CLASS_PATH constant 99 */ 100 @Deprecated ClassPath()101 public ClassPath() { 102 this(getClassPath()); 103 } 104 105 /** @return used class path string 106 */ 107 @Override toString()108 public String toString() { 109 if (parent != null) { 110 return parent + File.pathSeparator + class_path; 111 } 112 return class_path; 113 } 114 115 @Override hashCode()116 public int hashCode() { 117 if (parent != null) { 118 return class_path.hashCode() + parent.hashCode(); 119 } 120 return class_path.hashCode(); 121 } 122 123 124 @Override equals( final Object o )125 public boolean equals( final Object o ) { 126 if (o instanceof ClassPath) { 127 final ClassPath cp = (ClassPath)o; 128 return class_path.equals(cp.toString()); 129 } 130 return false; 131 } 132 133 getPathComponents( final String path, final List<String> list )134 private static void getPathComponents( final String path, final List<String> list ) { 135 if (path != null) { 136 final StringTokenizer tok = new StringTokenizer(path, File.pathSeparator); 137 while (tok.hasMoreTokens()) { 138 final String name = tok.nextToken(); 139 final File file = new File(name); 140 if (file.exists()) { 141 list.add(name); 142 } 143 } 144 } 145 } 146 147 148 /** Checks for class path components in the following properties: 149 * "java.class.path", "sun.boot.class.path", "java.ext.dirs" 150 * 151 * @return class path as used by default by BCEL 152 */ 153 // @since 6.0 no longer final getClassPath()154 public static String getClassPath() { 155 final String class_path = System.getProperty("java.class.path"); 156 final String boot_path = System.getProperty("sun.boot.class.path"); 157 final String ext_path = System.getProperty("java.ext.dirs"); 158 final List<String> list = new ArrayList<>(); 159 getPathComponents(class_path, list); 160 getPathComponents(boot_path, list); 161 final List<String> dirs = new ArrayList<>(); 162 getPathComponents(ext_path, dirs); 163 for (final String d : dirs) { 164 final File ext_dir = new File(d); 165 final String[] extensions = ext_dir.list(ARCHIVE_FILTER); 166 if (extensions != null) { 167 for (final String extension : extensions) { 168 list.add(ext_dir.getPath() + File.separatorChar + extension); 169 } 170 } 171 } 172 final StringBuilder buf = new StringBuilder(); 173 String separator = ""; 174 for (final String path : list) { 175 buf.append(separator); 176 separator = File.pathSeparator; 177 buf.append(path); 178 } 179 return buf.toString().intern(); 180 } 181 182 183 /** 184 * @param name fully qualified class name, e.g. java.lang.String 185 * @return input stream for class 186 */ getInputStream( final String name )187 public InputStream getInputStream( final String name ) throws IOException { 188 return getInputStream(name.replace('.', '/'), ".class"); 189 } 190 191 192 /** 193 * Return stream for class or resource on CLASSPATH. 194 * 195 * @param name fully qualified file name, e.g. java/lang/String 196 * @param suffix file name ends with suff, e.g. .java 197 * @return input stream for file on class path 198 */ getInputStream( final String name, final String suffix )199 public InputStream getInputStream( final String name, final String suffix ) throws IOException { 200 InputStream is = null; 201 try { 202 is = getClass().getClassLoader().getResourceAsStream(name + suffix); // may return null 203 } catch (final Exception e) { 204 // ignored 205 } 206 if (is != null) { 207 return is; 208 } 209 return getClassFile(name, suffix).getInputStream(); 210 } 211 212 /** 213 * @param name fully qualified resource name, e.g. java/lang/String.class 214 * @return InputStream supplying the resource, or null if no resource with that name. 215 * @since 6.0 216 */ getResourceAsStream(final String name)217 public InputStream getResourceAsStream(final String name) { 218 for (final PathEntry path : paths) { 219 InputStream is; 220 if ((is = path.getResourceAsStream(name)) != null) { 221 return is; 222 } 223 } 224 return null; 225 } 226 227 /** 228 * @param name fully qualified resource name, e.g. java/lang/String.class 229 * @return URL supplying the resource, or null if no resource with that name. 230 * @since 6.0 231 */ getResource(final String name)232 public URL getResource(final String name) { 233 for (final PathEntry path : paths) { 234 URL url; 235 if ((url = path.getResource(name)) != null) { 236 return url; 237 } 238 } 239 return null; 240 } 241 242 /** 243 * @param name fully qualified resource name, e.g. java/lang/String.class 244 * @return An Enumeration of URLs supplying the resource, or an 245 * empty Enumeration if no resource with that name. 246 * @since 6.0 247 */ getResources(final String name)248 public Enumeration<URL> getResources(final String name) { 249 final Vector<URL> results = new Vector<>(); 250 for (final PathEntry path : paths) { 251 URL url; 252 if ((url = path.getResource(name)) != null) { 253 results.add(url); 254 } 255 } 256 return results.elements(); 257 } 258 259 /** 260 * @param name fully qualified file name, e.g. java/lang/String 261 * @param suffix file name ends with suff, e.g. .java 262 * @return class file for the java class 263 */ getClassFile( final String name, final String suffix )264 public ClassFile getClassFile( final String name, final String suffix ) throws IOException { 265 ClassFile cf = null; 266 267 if (parent != null) { 268 cf = parent.getClassFileInternal(name, suffix); 269 } 270 271 if (cf == null) { 272 cf = getClassFileInternal(name, suffix); 273 } 274 275 if (cf != null) { 276 return cf; 277 } 278 279 throw new IOException("Couldn't find: " + name + suffix); 280 } 281 getClassFileInternal(final String name, final String suffix)282 private ClassFile getClassFileInternal(final String name, final String suffix) throws IOException { 283 284 for (final PathEntry path : paths) { 285 final ClassFile cf = path.getClassFile(name, suffix); 286 287 if(cf != null) { 288 return cf; 289 } 290 } 291 292 return null; 293 } 294 295 296 /** 297 * @param name fully qualified class name, e.g. java.lang.String 298 * @return input stream for class 299 */ getClassFile( final String name )300 public ClassFile getClassFile( final String name ) throws IOException { 301 return getClassFile(name, ".class"); 302 } 303 304 305 /** 306 * @param name fully qualified file name, e.g. java/lang/String 307 * @param suffix file name ends with suffix, e.g. .java 308 * @return byte array for file on class path 309 */ getBytes(final String name, final String suffix)310 public byte[] getBytes(final String name, final String suffix) throws IOException { 311 DataInputStream dis = null; 312 try (InputStream is = getInputStream(name, suffix)) { 313 if (is == null) { 314 throw new IOException("Couldn't find: " + name + suffix); 315 } 316 dis = new DataInputStream(is); 317 final byte[] bytes = new byte[is.available()]; 318 dis.readFully(bytes); 319 return bytes; 320 } finally { 321 if (dis != null) { 322 dis.close(); 323 } 324 } 325 } 326 327 328 /** 329 * @return byte array for class 330 */ getBytes( final String name )331 public byte[] getBytes( final String name ) throws IOException { 332 return getBytes(name, ".class"); 333 } 334 335 336 /** 337 * @param name name of file to search for, e.g. java/lang/String.java 338 * @return full (canonical) path for file 339 */ getPath( String name )340 public String getPath( String name ) throws IOException { 341 final int index = name.lastIndexOf('.'); 342 String suffix = ""; 343 if (index > 0) { 344 suffix = name.substring(index); 345 name = name.substring(0, index); 346 } 347 return getPath(name, suffix); 348 } 349 350 351 /** 352 * @param name name of file to search for, e.g. java/lang/String 353 * @param suffix file name suffix, e.g. .java 354 * @return full (canonical) path for file, if it exists 355 */ getPath( final String name, final String suffix )356 public String getPath( final String name, final String suffix ) throws IOException { 357 return getClassFile(name, suffix).getPath(); 358 } 359 360 private abstract static class PathEntry { 361 getClassFile( String name, String suffix )362 abstract ClassFile getClassFile( String name, String suffix ) throws IOException; getResource(String name)363 abstract URL getResource(String name); getResourceAsStream(String name)364 abstract InputStream getResourceAsStream(String name); 365 } 366 367 /** Contains information about file/ZIP entry of the Java class. 368 */ 369 public interface ClassFile { 370 371 /** @return input stream for class file. 372 */ getInputStream()373 InputStream getInputStream() throws IOException; 374 375 376 /** @return canonical path to class file. 377 */ getPath()378 String getPath(); 379 380 381 /** @return base path of found class, i.e. class is contained relative 382 * to that path, which may either denote a directory, or zip file 383 */ getBase()384 String getBase(); 385 386 387 /** @return modification time of class file. 388 */ getTime()389 long getTime(); 390 391 392 /** @return size of class file. 393 */ getSize()394 long getSize(); 395 } 396 397 private static class Dir extends PathEntry { 398 399 private final String dir; 400 401 Dir(final String d)402 Dir(final String d) { 403 dir = d; 404 } 405 406 @Override getResource(final String name)407 URL getResource(final String name) { 408 // Resource specification uses '/' whatever the platform 409 final File file = new File(dir + File.separatorChar + name.replace('/', File.separatorChar)); 410 try { 411 return file.exists() ? file.toURI().toURL() : null; 412 } catch (final MalformedURLException e) { 413 return null; 414 } 415 } 416 417 @Override getResourceAsStream(final String name)418 InputStream getResourceAsStream(final String name) { 419 // Resource specification uses '/' whatever the platform 420 final File file = new File(dir + File.separatorChar + name.replace('/', File.separatorChar)); 421 try { 422 return file.exists() ? new FileInputStream(file) : null; 423 } catch (final IOException e) { 424 return null; 425 } 426 } 427 428 @Override getClassFile( final String name, final String suffix )429 ClassFile getClassFile( final String name, final String suffix ) throws IOException { 430 final File file = new File(dir + File.separatorChar 431 + name.replace('.', File.separatorChar) + suffix); 432 return file.exists() ? new ClassFile() { 433 434 @Override 435 public InputStream getInputStream() throws IOException { 436 return new FileInputStream(file); 437 } 438 439 440 @Override 441 public String getPath() { 442 try { 443 return file.getCanonicalPath(); 444 } catch (final IOException e) { 445 return null; 446 } 447 } 448 449 450 @Override 451 public long getTime() { 452 return file.lastModified(); 453 } 454 455 456 @Override 457 public long getSize() { 458 return file.length(); 459 } 460 461 462 @Override 463 public String getBase() { 464 return dir; 465 } 466 } : null; 467 } 468 469 470 @Override toString()471 public String toString() { 472 return dir; 473 } 474 } 475 476 private static class Zip extends PathEntry { 477 478 private final ZipFile zip; 479 480 481 Zip(final ZipFile z) { 482 zip = z; 483 } 484 485 @Override 486 URL getResource(final String name) { 487 final ZipEntry entry = zip.getEntry(name); 488 try { 489 return (entry != null) ? new URL("jar:file:" + zip.getName() + "!/" + name) : null; 490 } catch (final MalformedURLException e) { 491 return null; 492 } 493 } 494 495 @Override 496 InputStream getResourceAsStream(final String name) { 497 final ZipEntry entry = zip.getEntry(name); 498 try { 499 return (entry != null) ? zip.getInputStream(entry) : null; 500 } catch (final IOException e) { 501 return null; 502 } 503 } 504 505 @Override 506 ClassFile getClassFile( final String name, final String suffix ) throws IOException { 507 final ZipEntry entry = zip.getEntry(name.replace('.', '/') + suffix); 508 509 if (entry == null) { 510 return null; 511 } 512 513 return new ClassFile() { 514 515 @Override 516 public InputStream getInputStream() throws IOException { 517 return zip.getInputStream(entry); 518 } 519 520 521 @Override 522 public String getPath() { 523 return entry.toString(); 524 } 525 526 527 @Override 528 public long getTime() { 529 return entry.getTime(); 530 } 531 532 533 @Override 534 public long getSize() { 535 return entry.getSize(); 536 } 537 538 539 @Override 540 public String getBase() { 541 return zip.getName(); 542 } 543 }; 544 } 545 } 546 } 547