1   // Copyright 2012- Bill Campbell, Swami Iyer and Bahar Akbal-Delibas
2   
3   package jminusminus;
4   
5   import java.io.*;
6   import java.util.ArrayList;
7   import java.util.StringTokenizer;
8   import java.util.zip.ZipEntry;
9   import java.util.zip.ZipFile;
10  
11  /**
12   * This class can be used to locate and load system, extension, and user-defined class files from
13   * directories and zip (jar) files.
14   */
15  class CLPath {
16      // Stores the individual directories, zip, and jar files from the class path.
17      private ArrayList<String> dirs;
18  
19      /**
20       * Returns a list of conceptual directories defining the class path.
21       *
22       * @param classPath the directory names defining the class path.
23       * @return a list of conceptual directories defining the class path.
24       */
25      private ArrayList<String> loadClassPath(String classPath) {
26          ArrayList<String> container = new ArrayList<String>();
27  
28          // Add directories/jars/zips from the classpath.
29          StringTokenizer entries = new StringTokenizer(classPath, File.pathSeparator);
30          while (entries.hasMoreTokens()) {
31              container.add(entries.nextToken());
32          }
33  
34          // Add system directories.
35          if (System.getProperty("sun.boot.class.path") != null) {
36              entries = new StringTokenizer(System.getProperty("sun.boot.class.path"),
37                      File.pathSeparator);
38              while (entries.hasMoreTokens()) {
39                  container.add(entries.nextToken());
40              }
41          } else {
42              String dir = System.getProperty("java.home") + File.separatorChar + "lib" +
43                      File.separatorChar + "rt.jar";
44              container.add(dir);
45          }
46          return container;
47      }
48  
49      /**
50       * Constructs a CLPath object.
51       */
52      public CLPath() {
53          this(null, null);
54      }
55  
56      /**
57       * Constructs a CLPath object given the directory names defining the path and the directory
58       * for the Java extension classes.
59       *
60       * @param path   the directory names defining the class path, separated by path separator.
61       * @param extdir the directory for the Java extension classes.
62       */
63      public CLPath(String path, String extdir) {
64          if (path == null) {
65              // No path specified, use CLASSPATH.
66              path = System.getProperty("java.class.path");
67          }
68          if (path == null) {
69              // Last resort, use current directory.
70              path = ".";
71          }
72          dirs = loadClassPath(path);
73          if (extdir == null) {
74              // Java extension classes.
75              extdir = System.getProperty("java.ext.dirs");
76          }
77          if (extdir != null) {
78              File extDirectory = new File(extdir);
79              if (extDirectory.isDirectory()) {
80                  File[] extFiles = extDirectory.listFiles();
81                  for (File file : extFiles) {
82                      if (file.isFile() &&
83                              (file.getName().endsWith(".zip") || file.getName().endsWith(".jar"))) {
84                          dirs.add(file.getName());
85                      } else {
86                          // Wrong suffix; ignore.
87                      }
88                  }
89              }
90          }
91      }
92  
93      /**
94       * Returns a CLInputStream instance for the class with specified name (fully-qualified;
95       * tokens separated by '/') or null if the class was not found.
96       *
97       * @param name the fully-qualified name of the class (eg, java/util/ArrayList).
98       * @return a CLInputStream instance for the class with specified name or null if the class
99       * was not found.
100      */
101     public CLInputStream loadClass(String name) {
102         CLInputStream reader = null;
103         for (int i = 0; i < dirs.size(); i++) {
104             String dir = dirs.get(i);
105             File file = new File(dir);
106             if (file.isDirectory()) {
107                 File theClass = new File(dir, name.replace('/', File.separatorChar) + ".class");
108                 if (theClass.canRead()) {
109                     try {
110                         reader = new CLInputStream(new BufferedInputStream(new
111                                 FileInputStream(theClass)));
112                     } catch (FileNotFoundException e) {
113                         // Ignore
114                     }
115                 }
116             } else if (file.isFile()) {
117                 try {
118                     ZipFile zip = new ZipFile(dir);
119                     ZipEntry entry = zip.getEntry(name + ".class");
120                     if (entry != null) {
121                         reader = new CLInputStream(zip.getInputStream(entry));
122                     }
123                 } catch (IOException e) {
124                     // Ignore
125                 }
126             } else {
127                 // Bogus entry; ignore
128             }
129         }
130         return reader;
131     }
132 }
133 
134 /**
135  * This class inherits from java.io.DataInputStream and provides an extra function for reading
136  * unsigned int from the input stream, which is required for reading Java class files.
137  */
138 class CLInputStream extends DataInputStream {
139     /**
140      * Constructs a CLInputStream object from the specified input stream.
141      *
142      * @param in input stream.
143      */
144     public CLInputStream(InputStream in) {
145         super(in);
146     }
147 
148     /**
149      * Reads four input bytes and returns a {@code long} value in the range 0 through 4294967295.
150      * Let a, b, c, d be the four bytes. The value returned is:
151      *
152      * <pre>
153      *   ( b[ 0 ] &amp; 0xFF ) &lt;&lt; 24 ) |
154      *   ( ( b[ 1 ] &amp; 0xFF ) &lt;&lt; 16 ) |
155      *   ( ( b[ 2 ] &amp; 0xFF ) &lt;&lt; 8 ) |
156      *   ( b[ 3 ] &amp; 0xFF )
157      * </pre>
158      *
159      * @return the unsigned 32-bit value.
160      * @throws EOFException if this stream reaches the end before reading all the bytes.
161      * @throws IOException  if an I/O error occurs.
162      */
163     public long readUnsignedInt() throws IOException {
164         byte[] b = new byte[4];
165         long mask = 0xFF, l;
166         in.read(b);
167         l = ((b[0] & mask) << 24) | ((b[1] & mask) << 16) | ((b[2] & mask) << 8) | (b[3] & mask);
168         return l;
169     }
170 }
171