001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 */
018 package org.apache.bcel.util;
019
020 import java.io.DataInputStream;
021 import java.io.File;
022 import java.io.FileInputStream;
023 import java.io.FilenameFilter;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.Serializable;
027 import java.net.MalformedURLException;
028 import java.net.URL;
029 import java.util.ArrayList;
030 import java.util.Enumeration;
031 import java.util.List;
032 import java.util.Locale;
033 import java.util.StringTokenizer;
034 import java.util.Vector;
035 import java.util.zip.ZipEntry;
036 import java.util.zip.ZipFile;
037
038 /**
039 * Responsible for loading (class) files from the CLASSPATH. Inspired by
040 * sun.tools.ClassPath.
041 *
042 * @version $Id: ClassPath.java 1152077 2011-07-29 02:29:42Z dbrosius $
043 * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
044 */
045 public class ClassPath implements Serializable {
046
047 private static final long serialVersionUID = 2099441438483340671L;
048 public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath();
049 private PathEntry[] paths;
050 private String class_path;
051 private ClassPath parent;
052
053 public ClassPath(ClassPath parent, String class_path) {
054 this(class_path);
055 this.parent = parent;
056 }
057
058 /**
059 * Search for classes in given path.
060 *
061 * @param class_path
062 */
063 public ClassPath(String class_path) {
064 this.class_path = class_path;
065 List<PathEntry> vec = new ArrayList<PathEntry>();
066 for (StringTokenizer tok = new StringTokenizer(class_path, System
067 .getProperty("path.separator")); tok.hasMoreTokens();) {
068 String path = tok.nextToken();
069 if (!path.equals("")) {
070 File file = new File(path);
071 try {
072 if (file.exists()) {
073 if (file.isDirectory()) {
074 vec.add(new Dir(path));
075 } else {
076 vec.add(new Zip(new ZipFile(file)));
077 }
078 }
079 } catch (IOException e) {
080 System.err.println("CLASSPATH component " + file + ": " + e);
081 }
082 }
083 }
084 paths = new PathEntry[vec.size()];
085 vec.toArray(paths);
086 }
087
088
089 /**
090 * Search for classes in CLASSPATH.
091 * @deprecated Use SYSTEM_CLASS_PATH constant
092 */
093 @Deprecated
094 public ClassPath() {
095 this(getClassPath());
096 }
097
098
099 /** @return used class path string
100 */
101 @Override
102 public String toString() {
103 if (parent != null) {
104 return parent.toString() + File.pathSeparator + class_path;
105 }
106 return class_path;
107 }
108
109 @Override
110 public int hashCode() {
111 if (parent != null) {
112 return class_path.hashCode() + parent.hashCode();
113 }
114 return class_path.hashCode();
115 }
116
117
118 @Override
119 public boolean equals( Object o ) {
120 if (o instanceof ClassPath) {
121 ClassPath cp = (ClassPath)o;
122 return class_path.equals(cp.toString());
123 }
124 return false;
125 }
126
127
128 private static final void getPathComponents( String path, List<String> list ) {
129 if (path != null) {
130 StringTokenizer tok = new StringTokenizer(path, File.pathSeparator);
131 while (tok.hasMoreTokens()) {
132 String name = tok.nextToken();
133 File file = new File(name);
134 if (file.exists()) {
135 list.add(name);
136 }
137 }
138 }
139 }
140
141
142 /** Checks for class path components in the following properties:
143 * "java.class.path", "sun.boot.class.path", "java.ext.dirs"
144 *
145 * @return class path as used by default by BCEL
146 */
147 public static final String getClassPath() {
148 String class_path = System.getProperty("java.class.path");
149 String boot_path = System.getProperty("sun.boot.class.path");
150 String ext_path = System.getProperty("java.ext.dirs");
151 List<String> list = new ArrayList<String>();
152 getPathComponents(class_path, list);
153 getPathComponents(boot_path, list);
154 List<String> dirs = new ArrayList<String>();
155 getPathComponents(ext_path, dirs);
156 for (String d : dirs) {
157 File ext_dir = new File(d);
158 String[] extensions = ext_dir.list(new FilenameFilter() {
159
160 public boolean accept( File dir, String name ) {
161 name = name.toLowerCase(Locale.ENGLISH);
162 return name.endsWith(".zip") || name.endsWith(".jar");
163 }
164 });
165 if (extensions != null) {
166 for (int i = 0; i < extensions.length; i++) {
167 list.add(ext_dir.getPath() + File.separatorChar + extensions[i]);
168 }
169 }
170 }
171 StringBuilder buf = new StringBuilder();
172 String separator = "";
173 for (String path : list) {
174 buf.append(separator);
175 separator = File.pathSeparator;
176 buf.append(path);
177 }
178 return buf.toString().intern();
179 }
180
181
182 /**
183 * @param name fully qualified class name, e.g. java.lang.String
184 * @return input stream for class
185 */
186 public InputStream getInputStream( String name ) throws IOException {
187 return getInputStream(name.replace('.', '/'), ".class");
188 }
189
190
191 /**
192 * Return stream for class or resource on CLASSPATH.
193 *
194 * @param name fully qualified file name, e.g. java/lang/String
195 * @param suffix file name ends with suff, e.g. .java
196 * @return input stream for file on class path
197 */
198 public InputStream getInputStream( String name, String suffix ) throws IOException {
199 InputStream is = null;
200 try {
201 is = getClass().getClassLoader().getResourceAsStream(name + suffix);
202 } catch (Exception e) {
203 }
204 if (is != null) {
205 return is;
206 }
207 return getClassFile(name, suffix).getInputStream();
208 }
209
210 /**
211 * @param name fully qualified resource name, e.g. java/lang/String.class
212 * @return InputStream supplying the resource, or null if no resource with that name.
213 */
214 public InputStream getResourceAsStream(String name) {
215 for (int i = 0; i < paths.length; i++) {
216 InputStream is;
217 if ((is = paths[i].getResourceAsStream(name)) != null) {
218 return is;
219 }
220 }
221 return null;
222 }
223
224 /**
225 * @param name fully qualified resource name, e.g. java/lang/String.class
226 * @return URL supplying the resource, or null if no resource with that name.
227 */
228 public URL getResource(String name) {
229 for (int i = 0; i < paths.length; i++) {
230 URL url;
231 if ((url = paths[i].getResource(name)) != null) {
232 return url;
233 }
234 }
235 return null;
236 }
237
238 /**
239 * @param name fully qualified resource name, e.g. java/lang/String.class
240 * @return An Enumeration of URLs supplying the resource, or an
241 * empty Enumeration if no resource with that name.
242 */
243 public Enumeration<URL> getResources(String name) {
244 Vector<URL> results = new Vector<URL>();
245 for (int i = 0; i < paths.length; i++) {
246 URL url;
247 if ((url = paths[i].getResource(name)) != null) {
248 results.add(url);
249 }
250 }
251 return results.elements();
252 }
253
254 /**
255 * @param name fully qualified file name, e.g. java/lang/String
256 * @param suffix file name ends with suff, e.g. .java
257 * @return class file for the java class
258 */
259 public ClassFile getClassFile( String name, String suffix ) throws IOException {
260 for (int i = 0; i < paths.length; i++) {
261 ClassFile cf = null;
262
263 if(parent != null) {
264 cf = parent.getClassFileInternal(name, suffix);
265 }
266
267 if(cf == null) {
268 cf = getClassFileInternal(name,suffix);
269 }
270
271 if(cf != null) {
272 return cf;
273 }
274 }
275
276 throw new IOException("Couldn't find: " + name + suffix);
277 }
278
279 private ClassFile getClassFileInternal(String name, String suffix) throws IOException {
280
281 for(int i=0; i < paths.length; i++) {
282 ClassFile cf = paths[i].getClassFile(name, suffix);
283
284 if(cf != null) {
285 return cf;
286 }
287 }
288
289 return null;
290 }
291
292
293 /**
294 * @param name fully qualified class name, e.g. java.lang.String
295 * @return input stream for class
296 */
297 public ClassFile getClassFile( String name ) throws IOException {
298 return getClassFile(name, ".class");
299 }
300
301
302 /**
303 * @param name fully qualified file name, e.g. java/lang/String
304 * @param suffix file name ends with suffix, e.g. .java
305 * @return byte array for file on class path
306 */
307 public byte[] getBytes( String name, String suffix ) throws IOException {
308 DataInputStream dis = null;
309 try {
310 InputStream is = getInputStream(name, suffix);
311 if (is == null) {
312 throw new IOException("Couldn't find: " + name + suffix);
313 }
314 dis = new DataInputStream(is);
315 byte[] bytes = new byte[is.available()];
316 dis.readFully(bytes);
317 return bytes;
318 } finally {
319 if (dis != null) {
320 dis.close();
321 }
322 }
323 }
324
325
326 /**
327 * @return byte array for class
328 */
329 public byte[] getBytes( String name ) throws IOException {
330 return getBytes(name, ".class");
331 }
332
333
334 /**
335 * @param name name of file to search for, e.g. java/lang/String.java
336 * @return full (canonical) path for file
337 */
338 public String getPath( String name ) throws IOException {
339 int index = name.lastIndexOf('.');
340 String suffix = "";
341 if (index > 0) {
342 suffix = name.substring(index);
343 name = name.substring(0, index);
344 }
345 return getPath(name, suffix);
346 }
347
348
349 /**
350 * @param name name of file to search for, e.g. java/lang/String
351 * @param suffix file name suffix, e.g. .java
352 * @return full (canonical) path for file, if it exists
353 */
354 public String getPath( String name, String suffix ) throws IOException {
355 return getClassFile(name, suffix).getPath();
356 }
357
358 private static abstract class PathEntry implements Serializable {
359
360 private static final long serialVersionUID = 6828494485207666122L;
361 abstract ClassFile getClassFile( String name, String suffix ) throws IOException;
362 abstract URL getResource(String name);
363 abstract InputStream getResourceAsStream(String name);
364 }
365
366 /** Contains information about file/ZIP entry of the Java class.
367 */
368 public interface ClassFile {
369
370 /** @return input stream for class file.
371 */
372 public abstract InputStream getInputStream() throws IOException;
373
374
375 /** @return canonical path to class file.
376 */
377 public abstract String getPath();
378
379
380 /** @return base path of found class, i.e. class is contained relative
381 * to that path, which may either denote a directory, or zip file
382 */
383 public abstract String getBase();
384
385
386 /** @return modification time of class file.
387 */
388 public abstract long getTime();
389
390
391 /** @return size of class file.
392 */
393 public abstract long getSize();
394 }
395
396 private static class Dir extends PathEntry {
397
398 private static final long serialVersionUID = 4374062802142373088L;
399 private String dir;
400
401
402 Dir(String d) {
403 dir = d;
404 }
405
406 @Override
407 URL getResource(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.toURL() : null;
412 } catch (MalformedURLException e) {
413 return null;
414 }
415 }
416
417 @Override
418 InputStream getResourceAsStream(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 (IOException e) {
424 return null;
425 }
426 }
427
428 @Override
429 ClassFile getClassFile( String name, 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 public InputStream getInputStream() throws IOException {
435 return new FileInputStream(file);
436 }
437
438
439 public String getPath() {
440 try {
441 return file.getCanonicalPath();
442 } catch (IOException e) {
443 return null;
444 }
445 }
446
447
448 public long getTime() {
449 return file.lastModified();
450 }
451
452
453 public long getSize() {
454 return file.length();
455 }
456
457
458 public String getBase() {
459 return dir;
460 }
461 } : null;
462 }
463
464
465 @Override
466 public String toString() {
467 return dir;
468 }
469 }
470
471 private static class Zip extends PathEntry {
472
473 private static final long serialVersionUID = -2210747632897905532L;
474 private ZipFile zip;
475
476
477 Zip(ZipFile z) {
478 zip = z;
479 }
480
481 @Override
482 URL getResource(String name) {
483 final ZipEntry entry = zip.getEntry(name);
484 try {
485 return (entry != null) ? new URL("jar:file:" + zip.getName() + "!/" + name) : null;
486 } catch (MalformedURLException e) {
487 return null;
488 }
489 }
490
491 @Override
492 InputStream getResourceAsStream(String name) {
493 final ZipEntry entry = zip.getEntry(name);
494 try {
495 return (entry != null) ? zip.getInputStream(entry) : null;
496 } catch (IOException e) {
497 return null;
498 }
499 }
500
501 @Override
502 ClassFile getClassFile( String name, String suffix ) throws IOException {
503 final ZipEntry entry = zip.getEntry(name.replace('.', '/') + suffix);
504
505 if (entry == null)
506 return null;
507
508 return new ClassFile() {
509
510 public InputStream getInputStream() throws IOException {
511 return zip.getInputStream(entry);
512 }
513
514
515 public String getPath() {
516 return entry.toString();
517 }
518
519
520 public long getTime() {
521 return entry.getTime();
522 }
523
524
525 public long getSize() {
526 return entry.getSize();
527 }
528
529
530 public String getBase() {
531 return zip.getName();
532 }
533 };
534 }
535 }
536 }