KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > soot > util > cfgcmd > AltClassLoader


1 /* Soot - a J*va Optimization Framework
2  * Copyright (C) 2003 John Jorgensen
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */

19
20 package soot.util.cfgcmd;
21
22 import java.io.File JavaDoc;
23 import java.io.FileInputStream JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.LinkedList JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.StringTokenizer JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.lang.reflect.*;
31 import soot.Singletons;
32 import soot.G;
33
34 /**
35  * <p>A {@link ClassLoader} that loads specified classes from a
36  * different class path than that given by the value of
37  * <code>java.class.path</code> in {@link System#getProperties()}.</p>
38  *
39  * <p>This class is part of Soot's test infrastructure. It allows
40  * loading multiple implementations of a class with a given
41  * name, and was written to compare different
42  * implementations of Soot's CFG representations.</p>
43  */

44 public class AltClassLoader extends ClassLoader JavaDoc {
45
46   private final static boolean DEBUG = false;
47
48   private String JavaDoc[] locations; // Locations in the alternate
49
// classpath.
50
private Map JavaDoc alreadyFound = new HashMap JavaDoc(); // Maps from already loaded
51
// classnames to their
52
// Class objects.
53

54   private Map JavaDoc nameToMangledName = new HashMap JavaDoc();// Maps from the names
55
// of classes to be
56
// loaded from the alternate
57
// classpath to mangled
58
// names to use for them.
59

60   private Map JavaDoc mangledNameToName = new HashMap JavaDoc();// Maps from the mangled names
61
// of classes back to their
62
// original names.
63

64
65
66   /**
67    * Constructs an <code>AltClassLoader</code> for inclusion in Soot's
68    * global variable manager, {@link G}.
69    *
70    * @param g guarantees that the constructor may only be called from
71    * {@link Singletons}.
72    */

73   public AltClassLoader(Singletons.Global g) {}
74
75
76   /**
77    * Returns the single instance of <code>AltClassLoader</code>, which
78    * loads classes from the classpath set by the most recent call to
79    * its {@link #setAltClassPath}.
80    *
81    * @return Soot's <code>AltClassLoader</code>.
82    */

83   public static AltClassLoader v() {
84     return G.v().soot_util_cfgcmd_AltClassLoader();
85   }
86
87
88   /**
89    * Sets the list of locations in the alternate classpath.
90    *
91    * @param classPath A list of directories and jar files to
92    * search for class files, delimited by
93    * {@link File#pathSeparator}.
94    */

95   public void setAltClassPath(String JavaDoc altClassPath) {
96     List JavaDoc locationList = new LinkedList JavaDoc();
97     for (StringTokenizer JavaDoc tokens =
98        new StringTokenizer JavaDoc(altClassPath, File.pathSeparator, false);
99      tokens.hasMoreTokens() ; ) {
100     String JavaDoc location = tokens.nextToken();
101       locationList.add(location);
102     }
103     locations = new String JavaDoc[locationList.size()];
104     locations = (String JavaDoc[]) locationList.toArray(locations);
105   }
106
107
108   /**
109    * Specifies the set of class names that the <code>AltClassLoader</code>
110    * should load from the alternate classpath instead of the
111    * regular classpath.
112    *
113    * @param classNames[] an array containing the names of classes to
114    * be loaded from the AltClassLoader.
115    */

116   public void setAltClasses(String JavaDoc[] classNames) {
117     nameToMangledName.clear();
118     for (int i = 0; i < classNames.length; i++) {
119       String JavaDoc origName = classNames[i];
120       String JavaDoc mangledName = mangleName(origName);
121       nameToMangledName.put(origName, mangledName);
122       mangledNameToName.put(mangledName, origName);
123     }
124   }
125
126   /**
127    * Mangles a classname so that it will not be found on the system
128    * classpath by our parent class loader, even if there is a class
129    * with the original name there. We use a crude heuristic to do this that
130    * happens to work with the names we have needed to mangle to date.
131    * The heuristic requires that <code>origName</code> include at least
132    * two dots (i.e., the class must be in a package, where
133    * the package name has at least two components). More sophisticated
134    * possibilities certainly exist, but they would require
135    * more thorough parsing of the class file.
136    *
137    * @param origName the name to be mangled.
138    * @return the mangled name.
139    * @throws IllegalArgumentException if <code>origName</code> is not
140    * amenable to our crude mangling.
141    */

142   private static String JavaDoc mangleName(String JavaDoc origName)
143   throws IllegalArgumentException JavaDoc {
144     final char dot = '.';
145     final char dotReplacement = '_';
146     StringBuffer JavaDoc mangledName = new StringBuffer JavaDoc(origName);
147     int replacements = 0;
148     int lastDot = origName.lastIndexOf(dot);
149     for (int nextDot = lastDot;
150      (nextDot = origName.lastIndexOf(dot, nextDot - 1)) >= 0; ) {
151       mangledName.setCharAt(nextDot, dotReplacement);
152       replacements++;
153     }
154     if (replacements <= 0) {
155       throw new IllegalArgumentException JavaDoc("AltClassLoader.mangleName()'s crude classname mangling cannot deal with " + origName);
156     }
157     return mangledName.toString();
158   }
159       
160
161   /**
162    * <p>
163    * Loads a class from either the regular classpath, or the alternate
164    * classpath, depending on whether it looks like we have already
165    * mangled its name.</p>
166    *
167    * <p> This method follows the steps provided by <a
168    * HREF="http://www.javaworld.com/javaworld/jw-03-2000/jw-03-classload.html#resources">Ken
169    * McCrary's ClasssLoader tutorial</a>.</p>
170    *
171    * @param maybeMangledName A string from which the desired class's
172    * name can be determined. It may have been mangled by {@link
173    * AltClassLoader#loadClass(String) AltClassLoader.loadClass()} so
174    * that the regular <code>ClassLoader</code> to which we are
175    * delegating won't load the class from the regular classpath.
176    * @return the loaded class.
177    * @throws ClassNotFoundException if the class cannot be loaded.
178    *
179    */

180   protected Class JavaDoc findClass(String JavaDoc maybeMangledName)
181     throws ClassNotFoundException JavaDoc {
182     if (DEBUG) {
183       G.v().out.println("AltClassLoader.findClass(" + maybeMangledName + ')');
184     }
185
186     Class JavaDoc result = (Class JavaDoc) alreadyFound.get(maybeMangledName);
187     if (result != null) {
188       return result;
189     }
190
191     String JavaDoc name = (String JavaDoc) mangledNameToName.get(maybeMangledName);
192     if (name == null) {
193       name = maybeMangledName;
194     }
195     String JavaDoc pathTail = "/" + name.replace('.', File.separatorChar) + ".class";
196
197     for (int i = 0; i < locations.length; i++) {
198       String JavaDoc path = locations[i] + pathTail;
199       try {
200     FileInputStream JavaDoc stream = new FileInputStream JavaDoc(path);
201     byte[] classBytes = new byte[stream.available()];
202     stream.read(classBytes);
203     replaceAltClassNames(classBytes);
204     result = defineClass(maybeMangledName, classBytes, 0, classBytes.length);
205     alreadyFound.put(maybeMangledName, result);
206     return result;
207       } catch (java.io.IOException JavaDoc e) {
208     // Try the next location.
209
} catch (ClassFormatError JavaDoc e) {
210     if (DEBUG) {
211       e.printStackTrace(G.v().out);
212     }
213     // Try the next location.
214
}
215     }
216     throw new ClassNotFoundException JavaDoc("Unable to find class" + name +
217                      " in alternate classpath");
218   }
219
220
221   /**
222    * <p>Loads a class, from the alternate classpath if the class's
223    * name has been included in the list of alternate classes with
224    * {@link #setAltClasses(String[]) setAltClasses()}, from the
225    * regular system classpath otherwise. When a alternate class is
226    * loaded, its references to other alternate classes are also
227    * resolved to the alternate classpath.
228    *
229    * @param name the name of the class to load.
230    * @return the loaded class.
231    * @throws ClassNotFoundException if the class cannot be loaded.
232    */

233   public Class JavaDoc loadClass(String JavaDoc name)
234   throws ClassNotFoundException JavaDoc {
235     if (DEBUG) {
236       G.v().out.println("AltClassLoader.loadClass(" + name + ")");
237     }
238
239     String JavaDoc nameForParent = (String JavaDoc) nameToMangledName.get(name);
240     if (nameForParent == null) {
241       // This is not an alternate class
242
nameForParent = name;
243     }
244
245     if (DEBUG) {
246       G.v().out.println("AltClassLoader.loadClass asking parent for " +
247             nameForParent);
248     }
249     return super.loadClass(nameForParent, false);
250   }
251
252
253   /**
254    * Replaces any occurrences in <code>classBytes</code> of
255    * classnames to be loaded from the alternate class path with
256    * the corresponding mangled names. Of course we should really
257    * parse the class pool properly, since the simple-minded, brute
258    * force replacment done here could produce problems with some
259    * combinations of classnames and class contents. But we've got away
260    * with this so far!
261    */

262   private void replaceAltClassNames(byte[] classBytes) {
263     for (Iterator JavaDoc it = nameToMangledName.entrySet().iterator();
264      it.hasNext(); ) {
265       Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
266       String JavaDoc origName = (String JavaDoc) entry.getKey();
267       origName = origName.replace('.', '/');
268       String JavaDoc mangledName = (String JavaDoc) entry.getValue();
269       mangledName = mangledName.replace('.', '/');
270       findAndReplace(classBytes, stringToUtf8Pattern(origName),
271              stringToUtf8Pattern(mangledName));
272       findAndReplace(classBytes, stringToTypeStringPattern(origName),
273              stringToTypeStringPattern(mangledName));
274     }
275   }
276
277   /**
278    * Returns the bytes that correspond to a
279    * CONSTANT_Utf8 constant pool entry containing
280    * the passed string.
281    */

282   private static byte[] stringToUtf8Pattern(String JavaDoc s) {
283     byte[] origBytes = s.getBytes();
284     int length = origBytes.length;
285     final byte CONSTANT_Utf8 = 1;
286     byte[] result = new byte[length + 3];
287     result[0] = CONSTANT_Utf8;
288     result[1] = (byte) (length & 0xff00);
289     result[2] = (byte) (length & 0x00ff);
290     for (int i = 0; i < length; i++) {
291       result[i+3] = origBytes[i];
292     }
293     return result;
294   }
295
296   /**
297    * Returns the bytes that correspond to a type signature string
298    * containing the passed string.
299    */

300   private static byte[] stringToTypeStringPattern(String JavaDoc s) {
301     byte[] origBytes = s.getBytes();
302     int length = origBytes.length;
303     byte[] result = new byte[length + 2];
304     result[0] = (byte) 'L';
305     for (int i = 0; i < length; i++) {
306       result[i+1] = origBytes[i];
307     }
308     result[length+1] = (byte) ';';
309     return result;
310   }
311
312
313   /**
314    * Replaces all occurrences of the <code>pattern</code> in <code>text</code>
315    * with <code>replacement</code>.
316    * @throws IllegalArgumentException if the lengths of <code>text</code>
317    * and <code>replacement</code> differ.
318    */

319   private static void findAndReplace(byte[] text, byte[] pattern,
320                   byte[] replacement)
321   throws IllegalArgumentException JavaDoc {
322     int patternLength = pattern.length;
323     if (patternLength != replacement.length) {
324       throw new IllegalArgumentException JavaDoc("findAndReplace(): The lengths of the pattern and replacement must match.");
325     }
326     int match = 0;
327     while ((match = findMatch(text, pattern, match)) >= 0) {
328       replace(text, replacement, match);
329       match += patternLength;
330     }
331   }
332
333
334   /**
335    * A naive string-searching algorithm for finding a pattern
336    * in a byte array.
337    *
338    * @param text the array to search in.
339    * @param pattern the string of bytes to search for.
340    * @param start the first position in text to search (0-based).
341    * @return the index in text where the first occurrence of
342    * <code>pattern</code> in <code>text</code> after <code>start</code>
343    * begins. Returns -1 if <code>pattern</code> does not occur
344    * in <code>text</code> after <code>start</code>.
345    */

346   private static int findMatch(byte[] text, byte[] pattern, int start) {
347     int textLength = text.length;
348     int patternLength = pattern.length;
349     nextBase:
350     for (int base = start; base < textLength; base++) {
351       for (int t = base, p = 0; p < patternLength; t++, p++) {
352     if (text[t] != pattern[p]) {
353       continue nextBase;
354     }
355       }
356       return base;
357     }
358     return -1;
359   }
360
361   /**
362    * Replace the <code>replacement.length</code> bytes in <code>text</code>
363    * starting at <code>start</code> with the bytes in <code>replacement</code>.
364    * @throws ArrayIndexOutOfBounds if there are not
365    * <code>replacement.length</code> remaining after <code>text[start]</code>.
366    */

367   private static void replace(byte[] text, byte[] replacement, int start) {
368     for (int t=start, p = 0; p < replacement.length; t++, p++) {
369       text[t] = replacement[p];
370     }
371   }
372     
373   /**
374    * <p>A main() entry for basic unit testing.</p>
375    *
376    * <p>Usage: path class ...</p>
377    */

378   public static void main(String JavaDoc[] argv) throws ClassNotFoundException JavaDoc {
379     AltClassLoader.v().setAltClassPath(argv[0]);
380     for (int i = 1; i < argv.length; i++) {
381       AltClassLoader.v().setAltClasses(new String JavaDoc[] {
382     argv[i]
383       });
384       G.v().out.println("main() loadClass(" + argv[i] + ")");
385       AltClassLoader.v().loadClass(argv[i]);
386     }
387   }
388     
389 }
390
Popular Tags