0001: /*
0002: * FindBugs - Find bugs in Java programs
0003: * Copyright (C) 2003-2005 University of Maryland
0004: *
0005: * This library is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU Lesser General Public
0007: * License as published by the Free Software Foundation; either
0008: * version 2.1 of the License, or (at your option) any later version.
0009: *
0010: * This library is distributed in the hope that it will be useful,
0011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0013: * Lesser General Public License for more details.
0014: *
0015: * You should have received a copy of the GNU Lesser General Public
0016: * License along with this library; if not, write to the Free Software
0017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018: */
0019:
0020: /*
0021: * Project.java
0022: *
0023: * Created on March 30, 2003, 2:22 PM
0024: */
0025:
0026: package edu.umd.cs.findbugs;
0027:
0028: import java.io.BufferedInputStream;
0029: import java.io.BufferedReader;
0030: import java.io.BufferedWriter;
0031: import java.io.File;
0032: import java.io.FileInputStream;
0033: import java.io.FileOutputStream;
0034: import java.io.FileWriter;
0035: import java.io.IOException;
0036: import java.io.InputStream;
0037: import java.io.OutputStream;
0038: import java.io.PrintWriter;
0039: import java.io.Reader;
0040: import java.net.MalformedURLException;
0041: import java.net.URL;
0042: import java.util.ArrayList;
0043: import java.util.Collection;
0044: import java.util.HashMap;
0045: import java.util.HashSet;
0046: import java.util.LinkedHashSet;
0047: import java.util.LinkedList;
0048: import java.util.List;
0049: import java.util.Map;
0050: import java.util.jar.Attributes;
0051: import java.util.jar.Manifest;
0052:
0053: import org.dom4j.DocumentException;
0054: import org.xml.sax.InputSource;
0055: import org.xml.sax.SAXException;
0056: import org.xml.sax.XMLReader;
0057: import org.xml.sax.helpers.XMLReaderFactory;
0058:
0059: import edu.umd.cs.findbugs.annotations.CheckForNull;
0060: import edu.umd.cs.findbugs.annotations.NonNull;
0061: import edu.umd.cs.findbugs.ba.AnalysisContext;
0062: import edu.umd.cs.findbugs.ba.URLClassPath;
0063: import edu.umd.cs.findbugs.filter.Filter;
0064: import edu.umd.cs.findbugs.util.Util;
0065: import edu.umd.cs.findbugs.xml.OutputStreamXMLOutput;
0066: import edu.umd.cs.findbugs.xml.XMLAttributeList;
0067: import edu.umd.cs.findbugs.xml.XMLOutput;
0068: import edu.umd.cs.findbugs.xml.XMLOutputUtil;
0069: import edu.umd.cs.findbugs.xml.XMLWriteable;
0070:
0071: /**
0072: * A project in the GUI.
0073: * This consists of some number of Jar files to analyze for bugs, and optionally
0074: * <p/>
0075: * <ul>
0076: * <li> some number of source directories, for locating the program's
0077: * source code
0078: * <li> some number of auxiliary classpath entries, for locating classes
0079: * referenced by the program which the user doesn't want to analyze
0080: * <li> some number of boolean options
0081: * </ul>
0082: *
0083: * @author David Hovemeyer
0084: */
0085: public class Project implements XMLWriteable {
0086: private static final boolean DEBUG = SystemProperties
0087: .getBoolean("findbugs.project.debug");
0088:
0089: private File currentWorkingDirectory;
0090: /**
0091: * Project filename.
0092: */
0093: @Deprecated
0094: private String projectFileName;
0095:
0096: private String projectName;
0097:
0098: /**
0099: * Options.
0100: */
0101: private Map<String, Boolean> optionsMap;
0102:
0103: /**
0104: * The list of project files.
0105: */
0106: private List<String> fileList;
0107:
0108: /**
0109: * The list of source directories.
0110: */
0111: private List<String> srcDirList;
0112:
0113: /**
0114: * The list of auxiliary classpath entries.
0115: */
0116: private List<String> auxClasspathEntryList;
0117:
0118: /**
0119: * Flag to indicate that this Project has been modified.
0120: */
0121: private boolean isModified;
0122:
0123: /**
0124: * Constant used to name anonymous projects.
0125: */
0126: public static final String UNNAMED_PROJECT = "<<unnamed project>>";
0127:
0128: private long timestamp = 0L;
0129:
0130: @NonNull
0131: private Filter suppressionFilter = new Filter();
0132:
0133: /**
0134: * Create an anonymous project.
0135: */
0136: public Project() {
0137: this .projectFileName = UNNAMED_PROJECT;
0138: optionsMap = new HashMap<String, Boolean>();
0139: optionsMap.put(RELATIVE_PATHS, Boolean.FALSE);
0140: fileList = new LinkedList<String>();
0141: srcDirList = new LinkedList<String>();
0142: auxClasspathEntryList = new LinkedList<String>();
0143: isModified = false;
0144: }
0145:
0146: /**
0147: * Return an exact copy of this Project.
0148: */
0149: public Project duplicate() {
0150: Project dup = new Project();
0151: dup.projectFileName = this .projectFileName;
0152: dup.currentWorkingDirectory = this .currentWorkingDirectory;
0153: dup.optionsMap.clear();
0154: dup.optionsMap.putAll(this .optionsMap);
0155: dup.fileList.addAll(this .fileList);
0156: dup.srcDirList.addAll(this .srcDirList);
0157: dup.auxClasspathEntryList.addAll(this .auxClasspathEntryList);
0158: dup.timestamp = timestamp;
0159:
0160: return dup;
0161: }
0162:
0163: /**
0164: * add information from project2 to this project
0165: */
0166: public void add(Project project2) {
0167: optionsMap.putAll(project2.optionsMap);
0168: fileList = appendWithoutDuplicates(fileList, project2.fileList);
0169: srcDirList = appendWithoutDuplicates(srcDirList,
0170: project2.srcDirList);
0171: auxClasspathEntryList = appendWithoutDuplicates(
0172: auxClasspathEntryList, project2.auxClasspathEntryList);
0173:
0174: }
0175:
0176: public static <T> List<T> appendWithoutDuplicates(List<T> lst1,
0177: List<T> lst2) {
0178: LinkedHashSet<T> joined = new LinkedHashSet<T>(lst1);
0179: joined.addAll(lst2);
0180: return new ArrayList<T>(joined);
0181:
0182: }
0183:
0184: public void setCurrentWorkingDirectory(File f) {
0185: this .currentWorkingDirectory = f;
0186: }
0187:
0188: /**
0189: * Return whether or not this Project has unsaved modifications.
0190: */
0191: public boolean isModified() {
0192: return isModified;
0193: }
0194:
0195: /**
0196: * Set whether or not this Project has unsaved modifications.
0197: */
0198: public void setModified(boolean isModified) {
0199: this .isModified = isModified;
0200: }
0201:
0202: /**
0203: * Get the project filename.
0204: */
0205: @Deprecated
0206: public String getProjectFileName() {
0207: return projectFileName;
0208: }
0209:
0210: /**
0211: * Set the project filename.
0212: *
0213: * @param projectFileName the new filename
0214: */
0215: @Deprecated
0216: public void setProjectFileName(String projectFileName) {
0217: this .projectFileName = projectFileName;
0218: }
0219:
0220: /**
0221: * Add a file to the project.
0222: *
0223: * @param fileName the file to add
0224: * @return true if the file was added, or false if the
0225: * file was already present
0226: */
0227: public boolean addFile(String fileName) {
0228: return addToListInternal(fileList, makeAbsoluteCWD(fileName));
0229: }
0230:
0231: /**
0232: * Add a source directory to the project.
0233: * @param dirName the directory to add
0234: * @return true if the source directory was added, or false if the
0235: * source directory was already present
0236: */
0237: public boolean addSourceDir(String dirName) {
0238: return addToListInternal(srcDirList, makeAbsoluteCWD(dirName));
0239: }
0240:
0241: /**
0242: * Retrieve the Options value.
0243: *
0244: * @param option the name of option to get
0245: * @return the value of the option
0246: */
0247: public boolean getOption(String option) {
0248: Boolean value = optionsMap.get(option);
0249: return value != null && value.booleanValue();
0250: }
0251:
0252: /**
0253: * Get the number of files in the project.
0254: *
0255: * @return the number of files in the project
0256: */
0257: public int getFileCount() {
0258: return fileList.size();
0259: }
0260:
0261: /**
0262: * Get the given file in the list of project files.
0263: *
0264: * @param num the number of the file in the list of project files
0265: * @return the name of the file
0266: */
0267: public String getFile(int num) {
0268: return fileList.get(num);
0269: }
0270:
0271: /**
0272: * Remove file at the given index in the list of project files
0273: *
0274: * @param num index of the file to remove in the list of project files
0275: */
0276: public void removeFile(int num) {
0277: fileList.remove(num);
0278: isModified = true;
0279: }
0280:
0281: /**
0282: * Get the list of files, directories, and zip files in the project.
0283: */
0284: public List<String> getFileList() {
0285: return fileList;
0286: }
0287:
0288: /**
0289: * Get the number of source directories in the project.
0290: *
0291: * @return the number of source directories in the project
0292: */
0293: public int getNumSourceDirs() {
0294: return srcDirList.size();
0295: }
0296:
0297: /**
0298: * Get the given source directory.
0299: *
0300: * @param num the number of the source directory
0301: * @return the source directory
0302: */
0303: public String getSourceDir(int num) {
0304: return srcDirList.get(num);
0305: }
0306:
0307: /**
0308: * Remove source directory at given index.
0309: *
0310: * @param num index of the source directory to remove
0311: */
0312: public void removeSourceDir(int num) {
0313: srcDirList.remove(num);
0314: isModified = true;
0315: }
0316:
0317: /**
0318: * Get project files as an array of Strings.
0319: */
0320: public String[] getFileArray() {
0321: return fileList.toArray(new String[fileList.size()]);
0322: }
0323:
0324: /**
0325: * Get source dirs as an array of Strings.
0326: */
0327: public String[] getSourceDirArray() {
0328: return srcDirList.toArray(new String[srcDirList.size()]);
0329: }
0330:
0331: /**
0332: * Get the source dir list.
0333: */
0334: public List<String> getSourceDirList() {
0335: return srcDirList;
0336: }
0337:
0338: /**
0339: * Add an auxiliary classpath entry
0340: *
0341: * @param auxClasspathEntry the entry
0342: * @return true if the entry was added successfully, or false
0343: * if the given entry is already in the list
0344: */
0345: public boolean addAuxClasspathEntry(String auxClasspathEntry) {
0346: return addToListInternal(auxClasspathEntryList,
0347: makeAbsoluteCWD(auxClasspathEntry));
0348: }
0349:
0350: /**
0351: * Get the number of auxiliary classpath entries.
0352: */
0353: public int getNumAuxClasspathEntries() {
0354: return auxClasspathEntryList.size();
0355: }
0356:
0357: /**
0358: * Get the n'th auxiliary classpath entry.
0359: */
0360: public String getAuxClasspathEntry(int n) {
0361: return auxClasspathEntryList.get(n);
0362: }
0363:
0364: /**
0365: * Remove the n'th auxiliary classpath entry.
0366: */
0367: public void removeAuxClasspathEntry(int n) {
0368: auxClasspathEntryList.remove(n);
0369: isModified = true;
0370: }
0371:
0372: /**
0373: * Return the list of aux classpath entries.
0374: */
0375: public List<String> getAuxClasspathEntryList() {
0376: return auxClasspathEntryList;
0377: }
0378:
0379: /**
0380: * Worklist item for finding implicit classpath entries.
0381: */
0382: private static class WorkListItem {
0383: private URL url;
0384:
0385: /**
0386: * Constructor.
0387: *
0388: * @param url the URL of the Jar or Zip file
0389: */
0390: public WorkListItem(URL url) {
0391: this .url = url;
0392: }
0393:
0394: /**
0395: * Get URL of Jar/Zip file.
0396: */
0397: public URL getURL() {
0398: return this .url;
0399: }
0400: }
0401:
0402: /**
0403: * Worklist for finding implicit classpath entries.
0404: */
0405: private static class WorkList {
0406: private LinkedList<WorkListItem> itemList;
0407: private HashSet<String> addedSet;
0408:
0409: /**
0410: * Constructor.
0411: * Creates an empty worklist.
0412: */
0413: public WorkList() {
0414: this .itemList = new LinkedList<WorkListItem>();
0415: this .addedSet = new HashSet<String>();
0416: }
0417:
0418: /**
0419: * Create a URL from a filename specified in the project file.
0420: */
0421: public URL createURL(String fileName)
0422: throws MalformedURLException {
0423: String protocol = URLClassPath.getURLProtocol(fileName);
0424: if (protocol == null) {
0425: fileName = "file:" + fileName;
0426: }
0427: return new URL(fileName);
0428: }
0429:
0430: /**
0431: * Create a URL of a file relative to another URL.
0432: */
0433: public URL createRelativeURL(URL base, String fileName)
0434: throws MalformedURLException {
0435: return new URL(base, fileName);
0436: }
0437:
0438: /**
0439: * Add a worklist item.
0440: *
0441: * @param item the WorkListItem representing a zip/jar file to be examined
0442: * @return true if the item was added, false if not (because it was
0443: * examined already)
0444: */
0445: public boolean add(WorkListItem item) {
0446: if (DEBUG) {
0447: System.out
0448: .println("Adding " + item.getURL().toString());
0449: }
0450: if (!addedSet.add(item.getURL().toString())) {
0451: if (DEBUG) {
0452: System.out.println("\t==> Already processed");
0453: }
0454: return false;
0455: }
0456:
0457: itemList.add(item);
0458: return true;
0459: }
0460:
0461: /**
0462: * Return whether or not the worklist is empty.
0463: */
0464: public boolean isEmpty() {
0465: return itemList.isEmpty();
0466: }
0467:
0468: /**
0469: * Get the next item in the worklist.
0470: */
0471: public WorkListItem getNextItem() {
0472: return itemList.removeFirst();
0473: }
0474: }
0475:
0476: /**
0477: * Return the list of implicit classpath entries. The implicit
0478: * classpath is computed from the closure of the set of jar files
0479: * that are referenced by the <code>"Class-Path"</code> attribute
0480: * of the manifest of the any jar file that is part of this project
0481: * or by the <code>"Class-Path"</code> attribute of any directly or
0482: * indirectly referenced jar. The referenced jar files that exist
0483: * are the list of implicit classpath entries.
0484: *
0485: * @deprecated FindBugs2 and ClassPathBuilder take care of this automatically
0486: */
0487: @Deprecated
0488: public List<String> getImplicitClasspathEntryList() {
0489: final LinkedList<String> implicitClasspath = new LinkedList<String>();
0490: WorkList workList = new WorkList();
0491:
0492: // Prime the worklist by adding the zip/jar files
0493: // in the project.
0494: for (String fileName : fileList) {
0495: try {
0496: URL url = workList.createURL(fileName);
0497: WorkListItem item = new WorkListItem(url);
0498: workList.add(item);
0499: } catch (MalformedURLException ignore) {
0500: // Ignore
0501: }
0502: }
0503:
0504: // Scan recursively.
0505: while (!workList.isEmpty()) {
0506: WorkListItem item = workList.getNextItem();
0507: processComponentJar(item.getURL(), workList,
0508: implicitClasspath);
0509: }
0510:
0511: return implicitClasspath;
0512: }
0513:
0514: /**
0515: * Examine the manifest of a single zip/jar file for implicit
0516: * classapth entries.
0517: *
0518: * @param jarFileURL URL of the zip/jar file
0519: * @param workList worklist of zip/jar files to examine
0520: * @param implicitClasspath list of implicit classpath entries found
0521: */
0522: private void processComponentJar(URL jarFileURL, WorkList workList,
0523: List<String> implicitClasspath) {
0524:
0525: if (DEBUG) {
0526: System.out.println("Processing " + jarFileURL.toString());
0527: }
0528:
0529: if (!jarFileURL.toString().endsWith(".zip")
0530: && !jarFileURL.toString().endsWith(".jar")) {
0531: return;
0532: }
0533:
0534: try {
0535: URL manifestURL = new URL("jar:" + jarFileURL.toString()
0536: + "!/META-INF/MANIFEST.MF");
0537:
0538: InputStream in = null;
0539: try {
0540: in = manifestURL.openStream();
0541: Manifest manifest = new Manifest(in);
0542:
0543: Attributes mainAttrs = manifest.getMainAttributes();
0544: String classPath = mainAttrs.getValue("Class-Path");
0545: if (classPath != null) {
0546: String[] fileList = classPath.split("\\s+");
0547:
0548: for (String jarFile : fileList) {
0549: URL referencedURL = workList.createRelativeURL(
0550: jarFileURL, jarFile);
0551: if (workList
0552: .add(new WorkListItem(referencedURL))) {
0553: implicitClasspath.add(referencedURL
0554: .toString());
0555: if (DEBUG) {
0556: System.out.println("Implicit jar: "
0557: + referencedURL.toString());
0558: }
0559: }
0560: }
0561: }
0562: } finally {
0563: if (in != null) {
0564: in.close();
0565: }
0566: }
0567: } catch (IOException ignore) {
0568: // Ignore
0569: }
0570: }
0571:
0572: private static final String OPTIONS_KEY = "[Options]";
0573: private static final String JAR_FILES_KEY = "[Jar files]";
0574: private static final String SRC_DIRS_KEY = "[Source dirs]";
0575: private static final String AUX_CLASSPATH_ENTRIES_KEY = "[Aux classpath entries]";
0576:
0577: // Option keys
0578: public static final String RELATIVE_PATHS = "relative_paths";
0579:
0580: /**
0581: * Save the project to an output file.
0582: *
0583: * @param outputFile name of output file
0584: * @param useRelativePaths true if the project should be written
0585: * using only relative paths
0586: * @param relativeBase if useRelativePaths is true,
0587: * this file is taken as the base directory in terms of which
0588: * all files should be made relative
0589: * @throws IOException if an error occurs while writing
0590: */
0591: @Deprecated
0592: public void write(String outputFile, boolean useRelativePaths,
0593: String relativeBase) throws IOException {
0594: PrintWriter writer = new PrintWriter(new BufferedWriter(
0595: new FileWriter(outputFile)));
0596: try {
0597: writer.println(JAR_FILES_KEY);
0598: for (String jarFile : fileList) {
0599: if (useRelativePaths) {
0600: jarFile = convertToRelative(jarFile, relativeBase);
0601: }
0602: writer.println(jarFile);
0603: }
0604:
0605: writer.println(SRC_DIRS_KEY);
0606: for (String srcDir : srcDirList) {
0607: if (useRelativePaths) {
0608: srcDir = convertToRelative(srcDir, relativeBase);
0609: }
0610: writer.println(srcDir);
0611: }
0612:
0613: writer.println(AUX_CLASSPATH_ENTRIES_KEY);
0614: for (String auxClasspathEntry : auxClasspathEntryList) {
0615: if (useRelativePaths) {
0616: auxClasspathEntry = convertToRelative(
0617: auxClasspathEntry, relativeBase);
0618: }
0619: writer.println(auxClasspathEntry);
0620: }
0621:
0622: if (useRelativePaths) {
0623: writer.println(OPTIONS_KEY);
0624: writer.println(RELATIVE_PATHS + "=true");
0625: }
0626: } finally {
0627: writer.close();
0628: }
0629:
0630: // Project successfully saved
0631: isModified = false;
0632: }
0633:
0634: public static Project readXML(File f) throws IOException,
0635: DocumentException, SAXException {
0636: Project project = new Project();
0637: InputStream in = new BufferedInputStream(new FileInputStream(f));
0638: String tag = Util.getXMLType(in);
0639: SAXBugCollectionHandler handler;
0640: if (tag.equals("Project")) {
0641: handler = new SAXBugCollectionHandler(project, f);
0642: } else if (tag.equals("BugCollection")) {
0643: SortedBugCollection bugs = new SortedBugCollection();
0644: handler = new SAXBugCollectionHandler(bugs, project, f);
0645: } else {
0646: throw new IOException("Can't load a project from a " + tag
0647: + " file");
0648: }
0649: try {
0650: XMLReader xr = null;
0651: if (true) {
0652: try {
0653: xr = XMLReaderFactory.createXMLReader();
0654: } catch (SAXException e) {
0655: AnalysisContext.logError(
0656: "Couldn't create XMLReaderFactory", e);
0657: }
0658: }
0659:
0660: if (xr == null) {
0661: xr = new org.dom4j.io.aelfred.SAXDriver();
0662: }
0663: xr.setContentHandler(handler);
0664: xr.setErrorHandler(handler);
0665:
0666: Reader reader = Util.getReader(in);
0667:
0668: xr.parse(new InputSource(reader));
0669: } finally {
0670: in.close();
0671: }
0672:
0673: // Presumably, project is now up-to-date
0674: project.setModified(false);
0675:
0676: return project;
0677:
0678: }
0679:
0680: public void writeXML(File f) throws IOException {
0681: OutputStream out = new FileOutputStream(f);
0682: XMLOutput xmlOutput = new OutputStreamXMLOutput(out);
0683: try {
0684: writeXML(xmlOutput);
0685: } finally {
0686: xmlOutput.finish();
0687: }
0688: }
0689:
0690: /**
0691: * Read the project from an input file.
0692: * This method should only be used on an empty Project
0693: * (created with the default constructor).
0694: *
0695: * @param inputFile name of the input file to read the project from
0696: * @throws IOException if an error occurs while reading
0697: */
0698: public void read(String inputFile) throws IOException {
0699: if (isModified) {
0700: throw new IllegalStateException(
0701: "Reading into a modified Project!");
0702: }
0703:
0704: // Make the input file absolute, if necessary
0705: File file = new File(inputFile);
0706: if (!file.isAbsolute()) {
0707: inputFile = file.getAbsolutePath();
0708: }
0709:
0710: // Store the project filename
0711: setProjectFileName(inputFile);
0712:
0713: BufferedReader reader = null;
0714:
0715: try {
0716: reader = new BufferedReader(Util.getFileReader(inputFile));
0717: String line;
0718: line = getLine(reader);
0719:
0720: if (line == null || !line.equals(JAR_FILES_KEY)) {
0721: throw new IOException(
0722: "Bad format: missing jar files key");
0723: }
0724: while ((line = getLine(reader)) != null
0725: && !line.equals(SRC_DIRS_KEY)) {
0726: addToListInternal(fileList, line);
0727: }
0728:
0729: if (line == null) {
0730: throw new IOException(
0731: "Bad format: missing source dirs key");
0732: }
0733: while ((line = getLine(reader)) != null
0734: && !line.equals(AUX_CLASSPATH_ENTRIES_KEY)) {
0735: addToListInternal(srcDirList, line);
0736: }
0737:
0738: // The list of aux classpath entries is optional
0739: if (line != null) {
0740: while ((line = getLine(reader)) != null) {
0741: if (line.equals(OPTIONS_KEY)) {
0742: break;
0743: }
0744: addToListInternal(auxClasspathEntryList, line);
0745: }
0746: }
0747:
0748: // The Options section is also optional
0749: if (line != null && line.equals(OPTIONS_KEY)) {
0750: while ((line = getLine(reader)) != null
0751: && !line.equals(JAR_FILES_KEY)) {
0752: parseOption(line);
0753: }
0754: }
0755:
0756: // If this project has the relative paths option set,
0757: // resolve all internal relative paths into absolute
0758: // paths, using the absolute path of the project
0759: // file as a base directory.
0760: if (getOption(RELATIVE_PATHS)) {
0761: makeListAbsoluteProject(fileList);
0762: makeListAbsoluteProject(srcDirList);
0763: makeListAbsoluteProject(auxClasspathEntryList);
0764: }
0765:
0766: // Clear the modification flag set by the various "add" methods.
0767: isModified = false;
0768: } finally {
0769: if (reader != null) {
0770: reader.close();
0771: }
0772: }
0773: }
0774:
0775: /**
0776: * Read a line from a BufferedReader, ignoring blank lines
0777: * and comments.
0778: */
0779: private static String getLine(BufferedReader reader)
0780: throws IOException {
0781: String line;
0782: while ((line = reader.readLine()) != null) {
0783: line = line.trim();
0784: if (!line.equals("") && !line.startsWith("#")) {
0785: break;
0786: }
0787: }
0788: return line;
0789: }
0790:
0791: public String projectNameFromProjectFileName() {
0792: String name = projectFileName;
0793: int lastSep = name.lastIndexOf(File.separatorChar);
0794: if (lastSep >= 0) {
0795: name = name.substring(lastSep + 1);
0796: }
0797: int dot = name.lastIndexOf('.');
0798: if (dot >= 0) {
0799: name = name.substring(0, dot);
0800: }
0801: return name;
0802:
0803: }
0804:
0805: /**
0806: * Convert to a string in a nice (displayable) format.
0807: */
0808: @Override
0809: public String toString() {
0810: if (projectName != null) {
0811: return projectName;
0812: }
0813: // TODO Andrei: if this old stuff is not more used, delete it
0814: String name = projectFileName;
0815: int lastSep = name.lastIndexOf(File.separatorChar);
0816: if (lastSep >= 0) {
0817: name = name.substring(lastSep + 1);
0818: }
0819: //int dot = name.lastIndexOf('.');
0820: //Don't hide every suffix--some are informative and/or disambiguative.
0821: int dot = (name.endsWith(".fb") ? name.length() - 3 : -1);
0822: if (dot >= 0) {
0823: name = name.substring(0, dot);
0824: }
0825: return name;
0826: }
0827:
0828: /**
0829: * Transform a user-entered filename into a proper filename,
0830: * by adding the ".fb" file extension if it isn't already present.
0831: */
0832: public static String transformFilename(String fileName) {
0833: if (!fileName.endsWith(".fb")) {
0834: fileName = fileName + ".fb";
0835: }
0836: return fileName;
0837: }
0838:
0839: static final String JAR_ELEMENT_NAME = "Jar";
0840: static final String AUX_CLASSPATH_ENTRY_ELEMENT_NAME = "AuxClasspathEntry";
0841: static final String SRC_DIR_ELEMENT_NAME = "SrcDir";
0842: static final String FILENAME_ATTRIBUTE_NAME = "filename";
0843: static final String PROJECTNAME_ATTRIBUTE_NAME = "projectName";
0844:
0845: public void writeXML(XMLOutput xmlOutput) throws IOException {
0846: writeXML(xmlOutput, null);
0847: }
0848:
0849: public void writeXML(XMLOutput xmlOutput, @CheckForNull
0850: Object destination) throws IOException {
0851: XMLAttributeList attributeList = new XMLAttributeList()
0852: .addAttribute(FILENAME_ATTRIBUTE_NAME,
0853: getProjectFileName());
0854: if (getProjectName() != null) {
0855: attributeList = attributeList.addAttribute(
0856: PROJECTNAME_ATTRIBUTE_NAME, getProjectName());
0857: }
0858: xmlOutput.openTag(BugCollection.PROJECT_ELEMENT_NAME,
0859: attributeList);
0860:
0861: XMLOutputUtil.writeElementList(xmlOutput, JAR_ELEMENT_NAME,
0862: fileList);
0863: XMLOutputUtil
0864: .writeElementList(xmlOutput,
0865: AUX_CLASSPATH_ENTRY_ELEMENT_NAME,
0866: auxClasspathEntryList);
0867: XMLOutputUtil.writeElementList(xmlOutput, SRC_DIR_ELEMENT_NAME,
0868: srcDirList);
0869:
0870: if (suppressionFilter != null && !suppressionFilter.isEmpty()) {
0871: xmlOutput.openTag("SuppressionFilter");
0872: suppressionFilter.writeBodyAsXML(xmlOutput);
0873: xmlOutput.closeTag("SuppressionFilter");
0874: }
0875: xmlOutput.closeTag(BugCollection.PROJECT_ELEMENT_NAME);
0876: }
0877:
0878: List<String> makeRelative(List<String> files, @CheckForNull
0879: Object destination) {
0880: if (destination == null) {
0881: return files;
0882: }
0883: if (currentWorkingDirectory == null) {
0884: return files;
0885: }
0886: if (destination instanceof File) {
0887: File where = (File) destination;
0888: if (where.getParentFile().equals(currentWorkingDirectory)) {
0889: List<String> result = new ArrayList<String>(files
0890: .size());
0891: String root = where.getParent();
0892: for (String s : files) {
0893: if (s.startsWith(root)) {
0894: result.add(s.substring(root.length()));
0895: } else {
0896: result.add(s);
0897: }
0898: }
0899: return result;
0900: }
0901: }
0902: return files;
0903: }
0904:
0905: /**
0906: * Parse one line in the [Options] section.
0907: *
0908: * @param option one line in the [Options] section
0909: */
0910: private void parseOption(String option) throws IOException {
0911: int equalPos = option.indexOf("=");
0912: if (equalPos < 0) {
0913: throw new IOException("Bad format: invalid option format");
0914: }
0915: String name = option.substring(0, equalPos);
0916: String value = option.substring(equalPos + 1);
0917: optionsMap.put(name, Boolean.valueOf(value));
0918: }
0919:
0920: /**
0921: * Hack for whether files are case insensitive.
0922: * For now, we'll assume that Windows is the only
0923: * case insensitive OS. (OpenVMS users,
0924: * feel free to submit a patch :-)
0925: */
0926: private static final boolean FILE_IGNORE_CASE = SystemProperties
0927: .getProperty("os.name", "unknown").startsWith("Windows");
0928:
0929: /**
0930: * Converts a full path to a relative path if possible
0931: *
0932: * @param srcFile path to convert
0933: * @return the converted filename
0934: */
0935: private String convertToRelative(String srcFile, String base) {
0936: String slash = SystemProperties.getProperty("file.separator");
0937:
0938: if (FILE_IGNORE_CASE) {
0939: srcFile = srcFile.toLowerCase();
0940: base = base.toLowerCase();
0941: }
0942:
0943: if (base.equals(srcFile)) {
0944: return ".";
0945: }
0946:
0947: if (!base.endsWith(slash)) {
0948: base = base + slash;
0949: }
0950:
0951: if (base.length() <= srcFile.length()) {
0952: String root = srcFile.substring(0, base.length());
0953: if (root.equals(base)) {
0954: // Strip off the base directory, make relative
0955: return "."
0956: + SystemProperties
0957: .getProperty("file.separator")
0958: + srcFile.substring(base.length());
0959: }
0960: }
0961:
0962: //See if we can build a relative path above the base using .. notation
0963: int slashPos = srcFile.indexOf(slash);
0964: int branchPoint;
0965: if (slashPos >= 0) {
0966: String subPath = srcFile.substring(0, slashPos);
0967: if ((subPath.length() == 0) || base.startsWith(subPath)) {
0968: branchPoint = slashPos + 1;
0969: slashPos = srcFile.indexOf(slash, branchPoint);
0970: while (slashPos >= 0) {
0971: subPath = srcFile.substring(0, slashPos);
0972: if (base.startsWith(subPath)) {
0973: branchPoint = slashPos + 1;
0974: } else {
0975: break;
0976: }
0977: slashPos = srcFile.indexOf(slash, branchPoint);
0978: }
0979:
0980: int slashCount = 0;
0981: slashPos = base.indexOf(slash, branchPoint);
0982: while (slashPos >= 0) {
0983: slashCount++;
0984: slashPos = base.indexOf(slash, slashPos + 1);
0985: }
0986:
0987: StringBuffer path = new StringBuffer();
0988: String upDir = ".." + slash;
0989: for (int i = 0; i < slashCount; i++) {
0990: path.append(upDir);
0991: }
0992: path.append(srcFile.substring(branchPoint));
0993: return path.toString();
0994: }
0995: }
0996:
0997: return srcFile;
0998:
0999: }
1000:
1001: /**
1002: * Converts a relative path to an absolute path if possible.
1003: *
1004: * @param fileName path to convert
1005: * @return the converted filename
1006: */
1007: private String convertToAbsolute(String fileName)
1008: throws IOException {
1009: // At present relative paths are only calculated if the fileName is
1010: // below the project file. This need not be the case, and we could use ..
1011: // syntax to move up the tree. (To Be Added)
1012:
1013: File file = new File(fileName);
1014:
1015: if (!file.isAbsolute()) {
1016: // Only try to make the relative path absolute
1017: // if the project file is absolute.
1018: File projectFile = new File(projectFileName);
1019: if (projectFile.isAbsolute()) {
1020: // Get base directory (parent of the project file)
1021: String base = new File(projectFileName).getParent();
1022:
1023: // Make the file absolute in terms of the parent directory
1024: fileName = new File(base, fileName).getCanonicalPath();
1025: }
1026: }
1027: return fileName;
1028: }
1029:
1030: /**
1031: * Make the given filename absolute relative to the
1032: * current working directory.
1033: */
1034: private String makeAbsoluteCWD(String fileName) {
1035:
1036: boolean hasProtocol = (URLClassPath.getURLProtocol(fileName) != null);
1037: if (hasProtocol) {
1038: return fileName;
1039: }
1040:
1041: if (new File(fileName).isAbsolute()) {
1042: return fileName;
1043: }
1044: File relativeToCurrent = new File(currentWorkingDirectory,
1045: fileName);
1046: if (relativeToCurrent.exists()) {
1047: return relativeToCurrent.toString();
1048: }
1049: return fileName;
1050: }
1051:
1052: /**
1053: * Add a value to given list, making the Project modified
1054: * if the value is not already present in the list.
1055: *
1056: * @param list the list
1057: * @param value the value to be added
1058: * @return true if the value was not already present in the list,
1059: * false otherwise
1060: */
1061: private boolean addToListInternal(Collection<String> list,
1062: String value) {
1063: if (!list.contains(value)) {
1064: list.add(value);
1065: isModified = true;
1066: return true;
1067: } else {
1068: return false;
1069: }
1070: }
1071:
1072: /**
1073: * Make the given list of pathnames absolute relative
1074: * to the absolute path of the project file.
1075: */
1076: private void makeListAbsoluteProject(List<String> list)
1077: throws IOException {
1078: List<String> replace = new LinkedList<String>();
1079: for (String fileName : list) {
1080: fileName = convertToAbsolute(fileName);
1081: replace.add(fileName);
1082: }
1083:
1084: list.clear();
1085: list.addAll(replace);
1086: }
1087:
1088: /**
1089: * @param timestamp The timestamp to set.
1090: */
1091: public void setTimestamp(long timestamp) {
1092: this .timestamp = timestamp;
1093: }
1094:
1095: public void addTimestamp(long timestamp) {
1096: if (this .timestamp < timestamp) {
1097: this .timestamp = timestamp;
1098: }
1099: }
1100:
1101: /**
1102: * @return Returns the timestamp.
1103: */
1104: public long getTimestamp() {
1105: return timestamp;
1106: }
1107:
1108: /**
1109: * @param projectName The projectName to set.
1110: */
1111: public void setProjectName(String projectName) {
1112: this .projectName = projectName;
1113: }
1114:
1115: /**
1116: * @return Returns the projectName.
1117: */
1118: public String getProjectName() {
1119: return projectName;
1120: }
1121:
1122: /**
1123: * @param suppressionFilter The suppressionFilter to set.
1124: */
1125: public void setSuppressionFilter(Filter suppressionFilter) {
1126: this .suppressionFilter = suppressionFilter;
1127: }
1128:
1129: /**
1130: * @return Returns the suppressionFilter.
1131: */
1132: public Filter getSuppressionFilter() {
1133: if (suppressionFilter == null) {
1134: suppressionFilter = new Filter();
1135: }
1136: return suppressionFilter;
1137: }
1138: }
1139:
1140: // vim:ts=4
|