001: /* Soot - a J*va Optimization Framework
002: * Copyright (C) 2004 Ondrej Lhotak
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;
021:
022: import soot.options.*;
023: import java.util.*;
024: import java.util.zip.*;
025: import java.io.*;
026:
027: /** Provides utility methods to retrieve an input stream for a class name, given
028: * a classfile, or jimple or baf output files. */
029: public class SourceLocator {
030: public SourceLocator(Singletons.Global g) {
031: }
032:
033: public static SourceLocator v() {
034: return G.v().soot_SourceLocator();
035: }
036:
037: /** Given a class name, uses the soot-class-path to return a ClassSource for the given class. */
038: public ClassSource getClassSource(String className) {
039: if (classPath == null) {
040: classPath = explodeClassPath(Scene.v().getSootClassPath());
041: }
042: if (classProviders == null) {
043: setupClassProviders();
044: }
045: for (ClassProvider cp : classProviders) {
046: ClassSource ret = cp.find(className);
047: if (ret != null)
048: return ret;
049: }
050: return null;
051: }
052:
053: private void setupClassProviders() {
054: classProviders = new LinkedList<ClassProvider>();
055: switch (Options.v().src_prec()) {
056: case Options.src_prec_class:
057: classProviders.add(new CoffiClassProvider());
058: classProviders.add(new JimpleClassProvider());
059: classProviders.add(new JavaClassProvider());
060: break;
061: case Options.src_prec_only_class:
062: classProviders.add(new CoffiClassProvider());
063: break;
064: case Options.src_prec_java:
065: classProviders.add(new JavaClassProvider());
066: classProviders.add(new CoffiClassProvider());
067: classProviders.add(new JimpleClassProvider());
068: break;
069: case Options.src_prec_jimple:
070: classProviders.add(new JimpleClassProvider());
071: classProviders.add(new CoffiClassProvider());
072: classProviders.add(new JavaClassProvider());
073: break;
074: default:
075: throw new RuntimeException(
076: "Other source precedences are not currently supported.");
077: }
078: }
079:
080: private List<ClassProvider> classProviders;
081:
082: public void setClassProviders(List<ClassProvider> classProviders) {
083: this .classProviders = classProviders;
084: }
085:
086: private List<String> classPath;
087:
088: public List<String> classPath() {
089: return classPath;
090: }
091:
092: public void invalidateClassPath() {
093: classPath = null;
094: }
095:
096: private List<String> sourcePath;
097:
098: public List<String> sourcePath() {
099: if (sourcePath == null) {
100: sourcePath = new ArrayList<String>();
101: for (String dir : classPath) {
102: if (!isJar(dir))
103: sourcePath.add(dir);
104: }
105: }
106: return sourcePath;
107: }
108:
109: private boolean isJar(String path) {
110: File f = new File(path);
111: if (f.isFile() && f.canRead()) {
112: if (path.endsWith("zip") || path.endsWith("jar")) {
113: return true;
114: } else {
115: G.v().out
116: .println("Warning: the following soot-classpath entry is not a supported archive file (must be .zip or .jar): "
117: + path);
118: }
119: }
120: return false;
121: }
122:
123: public List<String> getClassesUnder(String aPath) {
124: List<String> fileNames = new ArrayList<String>();
125:
126: if (isJar(aPath)) {
127: List inputExtensions = new ArrayList(2);
128: inputExtensions.add(".class");
129: inputExtensions.add(".jimple");
130: inputExtensions.add(".java");
131:
132: try {
133: ZipFile archive = new ZipFile(aPath);
134: for (Enumeration entries = archive.entries(); entries
135: .hasMoreElements();) {
136: ZipEntry entry = (ZipEntry) entries.nextElement();
137: String entryName = entry.getName();
138: int extensionIndex = entryName.lastIndexOf('.');
139: if (extensionIndex >= 0) {
140: String entryExtension = entryName
141: .substring(extensionIndex);
142: if (inputExtensions.contains(entryExtension)) {
143: entryName = entryName.substring(0,
144: extensionIndex);
145: entryName = entryName.replace('/', '.');
146: fileNames.add(entryName);
147: }
148: }
149: }
150: } catch (IOException e) {
151: G.v().out.println("Error reading " + aPath + ": "
152: + e.toString());
153: throw new CompilationDeathException(
154: CompilationDeathException.COMPILATION_ABORTED);
155: }
156: } else {
157: File file = new File(aPath);
158:
159: File[] files = file.listFiles();
160: if (files == null) {
161: files = new File[1];
162: files[0] = file;
163: }
164:
165: for (File element : files) {
166: if (element.isDirectory()) {
167: List<String> l = getClassesUnder(aPath
168: + File.separatorChar + element.getName());
169: Iterator<String> it = l.iterator();
170: while (it.hasNext()) {
171: String s = it.next();
172: fileNames.add(element.getName() + "." + s);
173: }
174: } else {
175: String fileName = element.getName();
176:
177: if (fileName.endsWith(".class")) {
178: int index = fileName.lastIndexOf(".class");
179: fileNames.add(fileName.substring(0, index));
180: }
181:
182: if (fileName.endsWith(".jimple")) {
183: int index = fileName.lastIndexOf(".jimple");
184: fileNames.add(fileName.substring(0, index));
185: }
186:
187: if (fileName.endsWith(".java")) {
188: int index = fileName.lastIndexOf(".java");
189: fileNames.add(fileName.substring(0, index));
190: }
191: }
192: }
193: }
194: return fileNames;
195: }
196:
197: public String getFileNameFor(SootClass c, int rep) {
198: if (rep == Options.output_format_none)
199: return null;
200:
201: StringBuffer b = new StringBuffer();
202:
203: if (!Options.v().output_jar()) {
204: b.append(getOutputDir());
205: }
206:
207: if ((b.length() > 0)
208: && (b.charAt(b.length() - 1) != File.separatorChar))
209: b.append(File.separatorChar);
210:
211: if (rep != Options.output_format_dava) {
212: if (rep == Options.output_format_class) {
213: b.append(c.getName().replace('.', File.separatorChar));
214: } else {
215: b.append(c.getName());
216: }
217: b.append(getExtensionFor(rep));
218:
219: return b.toString();
220: }
221:
222: b.append("dava");
223: b.append(File.separatorChar);
224: {
225: String classPath = b.toString() + "classes";
226: File dir = new File(classPath);
227:
228: if (!dir.exists())
229: try {
230: dir.mkdirs();
231: } catch (SecurityException se) {
232: G.v().out.println("Unable to create " + classPath);
233: throw new CompilationDeathException(
234: CompilationDeathException.COMPILATION_ABORTED);
235: }
236: }
237:
238: b.append("src");
239: b.append(File.separatorChar);
240:
241: String fixedPackageName = c.getJavaPackageName();
242: if (fixedPackageName.equals("") == false) {
243: b.append(fixedPackageName.replace('.', File.separatorChar));
244: b.append(File.separatorChar);
245: }
246:
247: {
248: String path = b.toString();
249: File dir = new File(path);
250:
251: if (!dir.exists())
252: try {
253: dir.mkdirs();
254: } catch (SecurityException se) {
255: G.v().out.println("Unable to create " + path);
256: throw new CompilationDeathException(
257: CompilationDeathException.COMPILATION_ABORTED);
258: }
259: }
260:
261: b.append(c.getShortJavaStyleName());
262: b.append(".java");
263:
264: return b.toString();
265: }
266:
267: /* This is called after sootClassPath has been defined. */
268: public Set<String> classesInDynamicPackage(String str) {
269: HashSet<String> set = new HashSet<String>(0);
270: StringTokenizer strtok = new StringTokenizer(Scene.v()
271: .getSootClassPath(), String
272: .valueOf(File.pathSeparatorChar));
273: while (strtok.hasMoreTokens()) {
274: String path = strtok.nextToken();
275:
276: // For jimple files
277: List<String> l = getClassesUnder(path);
278: for (String filename : l) {
279: if (filename.startsWith(str))
280: set.add(filename);
281: }
282:
283: // For class files;
284: path = path + File.pathSeparatorChar;
285: StringTokenizer tokenizer = new StringTokenizer(str, ".");
286: while (tokenizer.hasMoreTokens()) {
287: path = path + tokenizer.nextToken();
288: if (tokenizer.hasMoreTokens())
289: path = path + File.pathSeparatorChar;
290: }
291: l = getClassesUnder(path);
292: for (String string : l)
293: set.add(str + "." + string);
294: }
295: return set;
296: }
297:
298: public String getExtensionFor(int rep) {
299: switch (rep) {
300: case Options.output_format_baf:
301: return ".baf";
302: case Options.output_format_b:
303: return ".b";
304: case Options.output_format_jimple:
305: return ".jimple";
306: case Options.output_format_jimp:
307: return ".jimp";
308: case Options.output_format_shimple:
309: return ".shimple";
310: case Options.output_format_shimp:
311: return ".shimp";
312: case Options.output_format_grimp:
313: return ".grimp";
314: case Options.output_format_grimple:
315: return ".grimple";
316: case Options.output_format_class:
317: return ".class";
318: case Options.output_format_dava:
319: return ".java";
320: case Options.output_format_jasmin:
321: return ".jasmin";
322: case Options.output_format_xml:
323: return ".xml";
324: default:
325: throw new RuntimeException();
326: }
327: }
328:
329: public String getOutputDir() {
330: String ret = Options.v().output_dir();
331: if (ret.length() == 0)
332: ret = "sootOutput";
333: File dir = new File(ret);
334:
335: if (!dir.exists()) {
336: try {
337: if (!Options.v().output_jar()) {
338: dir.mkdirs();
339: }
340: } catch (SecurityException se) {
341: G.v().out.println("Unable to create " + ret);
342: throw new CompilationDeathException(
343: CompilationDeathException.COMPILATION_ABORTED);
344: }
345: }
346: return ret;
347: }
348:
349: /** Explodes a class path into a list of individual class path entries. */
350: protected List<String> explodeClassPath(String classPath) {
351: List<String> ret = new ArrayList<String>();
352:
353: StringTokenizer tokenizer = new StringTokenizer(classPath,
354: File.pathSeparator);
355: while (tokenizer.hasMoreTokens()) {
356: String originalDir = tokenizer.nextToken();
357: String canonicalDir;
358: try {
359: canonicalDir = new File(originalDir).getCanonicalPath();
360: ret.add(canonicalDir);
361: } catch (IOException e) {
362: throw new CompilationDeathException(
363: "Couldn't resolve classpath entry "
364: + originalDir + ": " + e);
365: }
366: }
367: return ret;
368: }
369:
370: public static class FoundFile {
371: FoundFile(ZipFile zipFile, ZipEntry entry) {
372: this .zipFile = zipFile;
373: this .entry = entry;
374: }
375:
376: FoundFile(File file) {
377: this .file = file;
378: }
379:
380: public File file;
381: public ZipFile zipFile;
382: public ZipEntry entry;
383:
384: public InputStream inputStream() {
385: try {
386: if (file != null)
387: return new FileInputStream(file);
388: return doJDKBugWorkaround(
389: zipFile.getInputStream(entry), entry.getSize());
390: } catch (IOException e) {
391: throw new RuntimeException("Caught IOException " + e);
392: }
393: }
394: }
395:
396: private static InputStream doJDKBugWorkaround(InputStream is,
397: long size) throws IOException {
398:
399: int sz = (int) size;
400: byte[] buf = new byte[sz];
401:
402: final int N = 1024;
403: int ln = 0;
404: int count = 0;
405: while (sz > 0
406: && (ln = is.read(buf, count, Math.min(N, sz))) != -1) {
407: count += ln;
408: sz -= ln;
409: }
410: return new ByteArrayInputStream(buf);
411: }
412:
413: /** Searches for a file with the given name in the exploded classPath. */
414: public FoundFile lookupInClassPath(String fileName) {
415: for (String dir : classPath) {
416: FoundFile ret;
417: if (isJar(dir)) {
418: ret = lookupInJar(dir, fileName);
419: } else {
420: ret = lookupInDir(dir, fileName);
421: }
422: if (ret != null)
423: return ret;
424: }
425: return null;
426: }
427:
428: private FoundFile lookupInDir(String dir, String fileName) {
429: File f = new File(dir + File.separatorChar + fileName);
430: if (f.canRead()) {
431: return new FoundFile(f);
432: }
433: return null;
434: }
435:
436: private FoundFile lookupInJar(String jar, String fileName) {
437: try {
438: ZipFile jarFile = new ZipFile(jar);
439: ZipEntry entry = jarFile.getEntry(fileName);
440: if (entry == null)
441: return null;
442: return new FoundFile(jarFile, entry);
443: } catch (IOException e) {
444: throw new RuntimeException("Caught IOException " + e
445: + " looking in jar file " + jar + " for file "
446: + fileName);
447: }
448: }
449:
450: private HashMap<String, String> sourceToClassMap;
451:
452: public HashMap<String, String> getSourceToClassMap() {
453: return sourceToClassMap;
454: }
455:
456: public void setSourceToClassMap(HashMap<String, String> map) {
457: sourceToClassMap = map;
458: }
459:
460: public void addToSourceToClassMap(String key, String val) {
461: sourceToClassMap.put(key, val);
462: }
463:
464: /** Returns the name of the class in which the (possibly inner) class
465: * className appears. */
466: public String getSourceForClass(String className) {
467: String javaClassName = className;
468: if (className.indexOf("$") != -1) {
469: // class is an inner class and will be in
470: // Outer of Outer$Inner
471: javaClassName = className.substring(0, className
472: .indexOf("$"));
473: //System.out.println("cut off inner class: look for: "+javaClassName);
474: }
475: // always do this because an inner class could be in a class
476: // thats in the map
477: if (sourceToClassMap != null) {
478: //System.out.println("in source map: "+sourceToClassMap);
479: if (sourceToClassMap.get(javaClassName) != null) {
480: javaClassName = sourceToClassMap.get(javaClassName);
481: }
482: }
483: return javaClassName;
484: }
485: }
|