001: /* Soot - a J*va Optimization Framework
002: * Copyright (C) 2003 John Jorgensen
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the
016: * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
017: * Boston, MA 02111-1307, USA.
018: */
019:
020: package soot.util.cfgcmd;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.util.List;
025: import java.util.LinkedList;
026: import java.util.Map;
027: import java.util.HashMap;
028: import java.util.StringTokenizer;
029: import soot.Singletons;
030: import soot.G;
031:
032: /**
033: * <p>A {@link ClassLoader} that loads specified classes from a
034: * different class path than that given by the value of
035: * <code>java.class.path</code> in {@link System#getProperties()}.</p>
036: *
037: * <p>This class is part of Soot's test infrastructure. It allows
038: * loading multiple implementations of a class with a given
039: * name, and was written to compare different
040: * implementations of Soot's CFG representations.</p>
041: */
042: public class AltClassLoader extends ClassLoader {
043:
044: private final static boolean DEBUG = false;
045:
046: private String[] locations; // Locations in the alternate
047: // classpath.
048: private final Map<String, Class> alreadyFound = new HashMap<String, Class>(); // Maps from already loaded
049: // classnames to their
050: // Class objects.
051:
052: private final Map<String, String> nameToMangledName = new HashMap<String, String>();// Maps from the names
053: // of classes to be
054: // loaded from the alternate
055: // classpath to mangled
056: // names to use for them.
057:
058: private final Map<String, String> mangledNameToName = new HashMap<String, String>();// Maps from the mangled names
059:
060: // of classes back to their
061: // original names.
062:
063: /**
064: * Constructs an <code>AltClassLoader</code> for inclusion in Soot's
065: * global variable manager, {@link G}.
066: *
067: * @param g guarantees that the constructor may only be called from
068: * {@link Singletons}.
069: */
070: public AltClassLoader(Singletons.Global g) {
071: }
072:
073: /**
074: * Returns the single instance of <code>AltClassLoader</code>, which
075: * loads classes from the classpath set by the most recent call to
076: * its {@link #setAltClassPath}.
077: *
078: * @return Soot's <code>AltClassLoader</code>.
079: */
080: public static AltClassLoader v() {
081: return G.v().soot_util_cfgcmd_AltClassLoader();
082: }
083:
084: /**
085: * Sets the list of locations in the alternate classpath.
086: *
087: * @param classPath A list of directories and jar files to
088: * search for class files, delimited by
089: * {@link File#pathSeparator}.
090: */
091: public void setAltClassPath(String altClassPath) {
092: List<String> locationList = new LinkedList<String>();
093: for (StringTokenizer tokens = new StringTokenizer(altClassPath,
094: File.pathSeparator, false); tokens.hasMoreTokens();) {
095: String location = tokens.nextToken();
096: locationList.add(location);
097: }
098: locations = new String[locationList.size()];
099: locations = locationList.toArray(locations);
100: }
101:
102: /**
103: * Specifies the set of class names that the <code>AltClassLoader</code>
104: * should load from the alternate classpath instead of the
105: * regular classpath.
106: *
107: * @param classNames[] an array containing the names of classes to
108: * be loaded from the AltClassLoader.
109: */
110: public void setAltClasses(String[] classNames) {
111: nameToMangledName.clear();
112: for (String origName : classNames) {
113: String mangledName = mangleName(origName);
114: nameToMangledName.put(origName, mangledName);
115: mangledNameToName.put(mangledName, origName);
116: }
117: }
118:
119: /**
120: * Mangles a classname so that it will not be found on the system
121: * classpath by our parent class loader, even if there is a class
122: * with the original name there. We use a crude heuristic to do this that
123: * happens to work with the names we have needed to mangle to date.
124: * The heuristic requires that <code>origName</code> include at least
125: * two dots (i.e., the class must be in a package, where
126: * the package name has at least two components). More sophisticated
127: * possibilities certainly exist, but they would require
128: * more thorough parsing of the class file.
129: *
130: * @param origName the name to be mangled.
131: * @return the mangled name.
132: * @throws IllegalArgumentException if <code>origName</code> is not
133: * amenable to our crude mangling.
134: */
135: private static String mangleName(String origName)
136: throws IllegalArgumentException {
137: final char dot = '.';
138: final char dotReplacement = '_';
139: StringBuffer mangledName = new StringBuffer(origName);
140: int replacements = 0;
141: int lastDot = origName.lastIndexOf(dot);
142: for (int nextDot = lastDot; (nextDot = origName.lastIndexOf(
143: dot, nextDot - 1)) >= 0;) {
144: mangledName.setCharAt(nextDot, dotReplacement);
145: replacements++;
146: }
147: if (replacements <= 0) {
148: throw new IllegalArgumentException(
149: "AltClassLoader.mangleName()'s crude classname mangling cannot deal with "
150: + origName);
151: }
152: return mangledName.toString();
153: }
154:
155: /**
156: * <p>
157: * Loads a class from either the regular classpath, or the alternate
158: * classpath, depending on whether it looks like we have already
159: * mangled its name.</p>
160: *
161: * <p> This method follows the steps provided by <a
162: * href="http://www.javaworld.com/javaworld/jw-03-2000/jw-03-classload.html#resources">Ken
163: * McCrary's ClasssLoader tutorial</a>.</p>
164: *
165: * @param maybeMangledName A string from which the desired class's
166: * name can be determined. It may have been mangled by {@link
167: * AltClassLoader#loadClass(String) AltClassLoader.loadClass()} so
168: * that the regular <code>ClassLoader</code> to which we are
169: * delegating won't load the class from the regular classpath.
170: * @return the loaded class.
171: * @throws ClassNotFoundException if the class cannot be loaded.
172: *
173: */
174: protected Class findClass(String maybeMangledName)
175: throws ClassNotFoundException {
176: if (DEBUG) {
177: G.v().out.println("AltClassLoader.findClass("
178: + maybeMangledName + ')');
179: }
180:
181: Class result = alreadyFound.get(maybeMangledName);
182: if (result != null) {
183: return result;
184: }
185:
186: String name = mangledNameToName.get(maybeMangledName);
187: if (name == null) {
188: name = maybeMangledName;
189: }
190: String pathTail = "/" + name.replace('.', File.separatorChar)
191: + ".class";
192:
193: for (String element : locations) {
194: String path = element + pathTail;
195: try {
196: FileInputStream stream = new FileInputStream(path);
197: byte[] classBytes = new byte[stream.available()];
198: stream.read(classBytes);
199: replaceAltClassNames(classBytes);
200: result = defineClass(maybeMangledName, classBytes, 0,
201: classBytes.length);
202: alreadyFound.put(maybeMangledName, result);
203: return result;
204: } catch (java.io.IOException e) {
205: // Try the next location.
206: } catch (ClassFormatError e) {
207: if (DEBUG) {
208: e.printStackTrace(G.v().out);
209: }
210: // Try the next location.
211: }
212: }
213: throw new ClassNotFoundException("Unable to find class" + name
214: + " in alternate classpath");
215: }
216:
217: /**
218: * <p>Loads a class, from the alternate classpath if the class's
219: * name has been included in the list of alternate classes with
220: * {@link #setAltClasses(String[]) setAltClasses()}, from the
221: * regular system classpath otherwise. When a alternate class is
222: * loaded, its references to other alternate classes are also
223: * resolved to the alternate classpath.
224: *
225: * @param name the name of the class to load.
226: * @return the loaded class.
227: * @throws ClassNotFoundException if the class cannot be loaded.
228: */
229: public Class loadClass(String name) throws ClassNotFoundException {
230: if (DEBUG) {
231: G.v().out.println("AltClassLoader.loadClass(" + name + ")");
232: }
233:
234: String nameForParent = nameToMangledName.get(name);
235: if (nameForParent == null) {
236: // This is not an alternate class
237: nameForParent = name;
238: }
239:
240: if (DEBUG) {
241: G.v().out
242: .println("AltClassLoader.loadClass asking parent for "
243: + nameForParent);
244: }
245: return super .loadClass(nameForParent, false);
246: }
247:
248: /**
249: * Replaces any occurrences in <code>classBytes</code> of
250: * classnames to be loaded from the alternate class path with
251: * the corresponding mangled names. Of course we should really
252: * parse the class pool properly, since the simple-minded, brute
253: * force replacment done here could produce problems with some
254: * combinations of classnames and class contents. But we've got away
255: * with this so far!
256: */
257: private void replaceAltClassNames(byte[] classBytes) {
258: for (Object element : nameToMangledName.entrySet()) {
259: Map.Entry entry = (Map.Entry) element;
260: String origName = (String) entry.getKey();
261: origName = origName.replace('.', '/');
262: String mangledName = (String) entry.getValue();
263: mangledName = mangledName.replace('.', '/');
264: findAndReplace(classBytes, stringToUtf8Pattern(origName),
265: stringToUtf8Pattern(mangledName));
266: findAndReplace(classBytes,
267: stringToTypeStringPattern(origName),
268: stringToTypeStringPattern(mangledName));
269: }
270: }
271:
272: /**
273: * Returns the bytes that correspond to a
274: * CONSTANT_Utf8 constant pool entry containing
275: * the passed string.
276: */
277: private static byte[] stringToUtf8Pattern(String s) {
278: byte[] origBytes = s.getBytes();
279: int length = origBytes.length;
280: final byte CONSTANT_Utf8 = 1;
281: byte[] result = new byte[length + 3];
282: result[0] = CONSTANT_Utf8;
283: result[1] = (byte) (length & 0xff00);
284: result[2] = (byte) (length & 0x00ff);
285: for (int i = 0; i < length; i++) {
286: result[i + 3] = origBytes[i];
287: }
288: return result;
289: }
290:
291: /**
292: * Returns the bytes that correspond to a type signature string
293: * containing the passed string.
294: */
295: private static byte[] stringToTypeStringPattern(String s) {
296: byte[] origBytes = s.getBytes();
297: int length = origBytes.length;
298: byte[] result = new byte[length + 2];
299: result[0] = (byte) 'L';
300: for (int i = 0; i < length; i++) {
301: result[i + 1] = origBytes[i];
302: }
303: result[length + 1] = (byte) ';';
304: return result;
305: }
306:
307: /**
308: * Replaces all occurrences of the <code>pattern</code> in <code>text</code>
309: * with <code>replacement</code>.
310: * @throws IllegalArgumentException if the lengths of <code>text</code>
311: * and <code>replacement</code> differ.
312: */
313: private static void findAndReplace(byte[] text, byte[] pattern,
314: byte[] replacement) throws IllegalArgumentException {
315: int patternLength = pattern.length;
316: if (patternLength != replacement.length) {
317: throw new IllegalArgumentException(
318: "findAndReplace(): The lengths of the pattern and replacement must match.");
319: }
320: int match = 0;
321: while ((match = findMatch(text, pattern, match)) >= 0) {
322: replace(text, replacement, match);
323: match += patternLength;
324: }
325: }
326:
327: /**
328: * A naive string-searching algorithm for finding a pattern
329: * in a byte array.
330: *
331: * @param text the array to search in.
332: * @param pattern the string of bytes to search for.
333: * @param start the first position in text to search (0-based).
334: * @return the index in text where the first occurrence of
335: * <code>pattern</code> in <code>text</code> after <code>start</code>
336: * begins. Returns -1 if <code>pattern</code> does not occur
337: * in <code>text</code> after <code>start</code>.
338: */
339: private static int findMatch(byte[] text, byte[] pattern, int start) {
340: int textLength = text.length;
341: int patternLength = pattern.length;
342: nextBase: for (int base = start; base < textLength; base++) {
343: for (int t = base, p = 0; p < patternLength; t++, p++) {
344: if (text[t] != pattern[p]) {
345: continue nextBase;
346: }
347: }
348: return base;
349: }
350: return -1;
351: }
352:
353: /**
354: * Replace the <code>replacement.length</code> bytes in <code>text</code>
355: * starting at <code>start</code> with the bytes in <code>replacement</code>.
356: * @throws ArrayIndexOutOfBounds if there are not
357: * <code>replacement.length</code> remaining after <code>text[start]</code>.
358: */
359: private static void replace(byte[] text, byte[] replacement,
360: int start) {
361: for (int t = start, p = 0; p < replacement.length; t++, p++) {
362: text[t] = replacement[p];
363: }
364: }
365:
366: /**
367: * <p>A main() entry for basic unit testing.</p>
368: *
369: * <p>Usage: path class ...</p>
370: */
371: public static void main(String[] argv)
372: throws ClassNotFoundException {
373: AltClassLoader.v().setAltClassPath(argv[0]);
374: for (int i = 1; i < argv.length; i++) {
375: AltClassLoader.v().setAltClasses(new String[] { argv[i] });
376: G.v().out.println("main() loadClass(" + argv[i] + ")");
377: AltClassLoader.v().loadClass(argv[i]);
378: }
379: }
380:
381: }
|