001: /*
002: * Bytecode Analysis Framework
003: * Copyright (C) 2003,2004 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import java.io.File;
023: import java.io.FileNotFoundException;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.net.JarURLConnection;
027: import java.net.MalformedURLException;
028: import java.net.URL;
029: import java.util.Enumeration;
030: import java.util.LinkedHashMap;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.zip.ZipEntry;
035: import java.util.zip.ZipFile;
036:
037: import edu.umd.cs.findbugs.SourceLineAnnotation;
038: import edu.umd.cs.findbugs.SystemProperties;
039:
040: /**
041: * Class to open input streams on source files.
042: * It maintains a "source path", which is like a classpath,
043: * but for finding source files instead of class files.
044: */
045: public class SourceFinder {
046: private static final boolean DEBUG = SystemProperties
047: .getBoolean("srcfinder.debug");
048: private static final int CACHE_SIZE = 50;
049:
050: /* ----------------------------------------------------------------------
051: * Helper classes
052: * ---------------------------------------------------------------------- */
053:
054: /**
055: * Cache of SourceFiles.
056: * We use this to avoid repeatedly having to read
057: * frequently accessed source files.
058: */
059: private static class Cache extends
060: LinkedHashMap<String, SourceFile> {
061: /**
062: *
063: */
064: private static final long serialVersionUID = 1L;
065:
066: @Override
067: protected boolean removeEldestEntry(
068: Map.Entry<String, SourceFile> eldest) {
069: return size() >= CACHE_SIZE;
070: }
071: }
072:
073: /**
074: * A repository of source files.
075: */
076: private interface SourceRepository {
077: public boolean contains(String fileName);
078:
079: public boolean isPlatformDependent();
080:
081: public SourceFileDataSource getDataSource(String fileName);
082: }
083:
084: /**
085: * A directory containing source files.
086: */
087: private static class DirectorySourceRepository implements
088: SourceRepository {
089: private String baseDir;
090:
091: public DirectorySourceRepository(String baseDir) {
092: this .baseDir = baseDir;
093: }
094:
095: @Override
096: public String toString() {
097: return "DirectorySourceRepository:" + baseDir;
098: }
099:
100: public boolean contains(String fileName) {
101: File file = new File(getFullFileName(fileName));
102: boolean exists = file.exists();
103: if (DEBUG)
104: System.out.println("Exists " + exists + " for " + file);
105: return exists;
106: }
107:
108: public boolean isPlatformDependent() {
109: return true;
110: }
111:
112: public SourceFileDataSource getDataSource(String fileName) {
113: return new FileSourceFileDataSource(
114: getFullFileName(fileName));
115: }
116:
117: private String getFullFileName(String fileName) {
118: return baseDir + File.separator + fileName;
119: }
120: }
121:
122: static class JarURLConnectionSourceRepository extends
123: ZipSourceRepository {
124:
125: public JarURLConnectionSourceRepository(String url)
126: throws MalformedURLException, IOException {
127: super (((JarURLConnection) new URL("jar:" + url + "!/")
128: .openConnection()).getJarFile());
129:
130: if (DEBUG) {
131: System.out
132: .println("JarURLConnectionSourceRepository entries");
133: for (Enumeration<? extends ZipEntry> e = zipFile
134: .entries(); e.hasMoreElements();) {
135: ZipEntry ze = e.nextElement();
136: System.out.println(ze.getName());
137: }
138: }
139: }
140:
141: }
142:
143: /**
144: * A zip or jar archive containing source files.
145: */
146: static class ZipSourceRepository implements SourceRepository {
147: ZipFile zipFile;
148:
149: public ZipSourceRepository(ZipFile zipFile) {
150: this .zipFile = zipFile;
151: }
152:
153: public boolean contains(String fileName) {
154: return zipFile.getEntry(fileName) != null;
155: }
156:
157: public boolean isPlatformDependent() {
158: return false;
159: }
160:
161: public SourceFileDataSource getDataSource(String fileName) {
162: return new ZipSourceFileDataSource(zipFile, fileName);
163: }
164: }
165:
166: /* ----------------------------------------------------------------------
167: * Fields
168: * ---------------------------------------------------------------------- */
169:
170: private List<SourceRepository> repositoryList;
171: private Cache cache;
172:
173: /* ----------------------------------------------------------------------
174: * Public methods
175: * ---------------------------------------------------------------------- */
176:
177: /**
178: * Constructor.
179: */
180: public SourceFinder() {
181: if (DEBUG)
182: System.out.println("Debugging SourceFinder");
183: repositoryList = new LinkedList<SourceRepository>();
184: cache = new Cache();
185: }
186:
187: /**
188: * Set the list of source directories.
189: */
190: public void setSourceBaseList(List<String> sourceBaseList) {
191: for (String repos : sourceBaseList) {
192: if (repos.endsWith(".zip") || repos.endsWith(".jar")) {
193: // Zip or jar archive
194: try {
195: if (repos.startsWith("http:")
196: || repos.startsWith("https:")
197: || repos.startsWith("file:"))
198: repositoryList
199: .add(new JarURLConnectionSourceRepository(
200: repos));
201: else
202: repositoryList.add(new ZipSourceRepository(
203: new ZipFile(repos)));
204: } catch (IOException e) {
205: // Ignored - we won't use this archive
206: }
207: } else {
208: // Directory
209: repositoryList
210: .add(new DirectorySourceRepository(repos));
211: }
212: }
213: }
214:
215: /**
216: * Open an input stream on a source file in given package.
217: *
218: * @param packageName the name of the package containing the class whose source file is given
219: * @param fileName the unqualified name of the source file
220: * @return an InputStream on the source file
221: * @throws IOException if a matching source file cannot be found
222: */
223: public InputStream openSource(String packageName, String fileName)
224: throws IOException {
225: SourceFile sourceFile = findSourceFile(packageName, fileName);
226: return sourceFile.getInputStream();
227: }
228:
229: public InputStream openSource(SourceLineAnnotation source)
230: throws IOException {
231: SourceFile sourceFile = findSourceFile(source);
232: return sourceFile.getInputStream();
233: }
234:
235: public SourceFile findSourceFile(SourceLineAnnotation source)
236: throws IOException {
237: if (source.isSourceFileKnown())
238: return findSourceFile(source.getPackageName(), source
239: .getSourceFile());
240: String packageName = source.getPackageName();
241: String baseClassName = source.getClassName();
242: int i = baseClassName.lastIndexOf('.');
243: baseClassName = baseClassName.substring(i + 1);
244: int j = baseClassName.indexOf("$");
245: if (j >= 0)
246: baseClassName = baseClassName.substring(0, j);
247: return findSourceFile(packageName, baseClassName + ".java");
248:
249: }
250:
251: /**
252: * Open a source file in given package.
253: *
254: * @param packageName the name of the package containing the class whose source file is given
255: * @param fileName the unqualified name of the source file
256: * @return the source file
257: * @throws IOException if a matching source file cannot be found
258: */
259: public SourceFile findSourceFile(String packageName, String fileName)
260: throws IOException {
261: // On windows the fileName specification is different between a file in a directory tree, and a
262: // file in a zip file. In a directory tree the separator used is '\', while in a zip it's '/'
263: // Therefore for each repository figure out what kind it is and use the appropriate separator.
264:
265: // In all practicality, this code could just use the hardcoded '/' char, as windows can open
266: // files with this separator, but to allow for the mythical 'other' platform that uses an
267: // alternate separator, make a distinction
268:
269: // Create a fully qualified source filename using the package name for both directories and zips
270: String platformName = packageName.replace('.',
271: File.separatorChar)
272: + (packageName.length() > 0 ? File.separator : "")
273: + fileName;
274: String canonicalName = packageName.replace('.', '/')
275: + (packageName.length() > 0 ? "/" : "") + fileName;
276:
277: // Is the file in the cache already? Always cache it with the canonical name
278: SourceFile sourceFile = cache.get(canonicalName);
279: if (sourceFile != null)
280: return sourceFile;
281:
282: // Find this source file, add its data to the cache
283: if (DEBUG)
284: System.out.println("Trying " + fileName + " in package "
285: + packageName + "...");
286: // Query each element of the source path to find the requested source file
287: for (SourceRepository repos : repositoryList) {
288: fileName = repos.isPlatformDependent() ? platformName
289: : canonicalName;
290: if (DEBUG)
291: System.out.println("Looking in " + repos + " for "
292: + fileName);
293: if (repos.contains(fileName)) {
294: // Found it
295: sourceFile = new SourceFile(repos
296: .getDataSource(fileName));
297: cache.put(canonicalName, sourceFile); // always cache with canonicalName
298: return sourceFile;
299: }
300: }
301:
302: throw new FileNotFoundException("Can't find source file "
303: + fileName);
304: }
305: }
306:
307: // vim:ts=4
|