001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.project;
038:
039: import java.util.ArrayList;
040: import java.util.List; //import java.util.Vector;
041: import java.util.Date;
042: import java.text.DateFormat;
043: import java.text.SimpleDateFormat;
044: import java.util.Locale;
045: import java.io.*;
046:
047: import edu.rice.cs.plt.tuple.Pair;
048: import edu.rice.cs.plt.io.IOUtil;
049: import edu.rice.cs.plt.iter.IterUtil;
050: import edu.rice.cs.drjava.config.FileOption;
051: import edu.rice.cs.drjava.Version;
052: import edu.rice.cs.util.FileOps;
053: import edu.rice.cs.util.UnexpectedException;
054: import edu.rice.cs.util.swing.Utilities;
055: import edu.rice.cs.drjava.model.DocumentRegion;
056: import edu.rice.cs.drjava.model.debug.DebugBreakpointData;
057: import edu.rice.cs.drjava.model.debug.DebugWatchData;
058: import edu.rice.cs.drjava.model.debug.DebugException;
059:
060: import static edu.rice.cs.util.StringOps.*;
061:
062: /** The internal representation of a project; it is the internal analog of a project file. Includes support for
063: * writing corresponding project file.
064: */
065: public class ProjectProfile implements ProjectFileIR {
066: static final String MOD_DATE_FORMAT_STRING = "dd-MMM-yyyy HH:mm:ss";
067: static final DateFormat MOD_DATE_FORMAT = new SimpleDateFormat(
068: MOD_DATE_FORMAT_STRING, Locale.US);
069:
070: /* Private fields */
071:
072: private List<DocFile> _sourceFiles = new ArrayList<DocFile>();
073: private List<DocFile> _auxFiles = new ArrayList<DocFile>();
074: private List<String> _collapsedPaths = new ArrayList<String>();
075:
076: private File _buildDir = null;
077: private File _workDir = null;
078:
079: private List<File> _classPathFiles = new ArrayList<File>();
080:
081: private File _mainClass = null;
082:
083: /** root of project source tree. Invariant: _projectRoot.exists() */
084: private File _projectRoot;
085:
086: private File _projectFile; /* Invariant: _projectFile.getParentFile().exists() */
087:
088: private File _createJarFile = null;
089:
090: private int _createJarFlags = 0;
091:
092: private List<DocumentRegion> _bookmarks = new ArrayList<DocumentRegion>();
093: private List<DebugBreakpointData> _breakpoints = new ArrayList<DebugBreakpointData>();
094: private List<DebugWatchData> _watches = new ArrayList<DebugWatchData>();
095:
096: /** Constructs a File for fileName and forwards this call to the main constructor. */
097: public ProjectProfile(String fileName) throws IOException {
098: this (new File(fileName));
099: }
100:
101: /** Creates new ProjectProfiles with specifed project file name and project root that is parent folder of
102: * the project file. The project file presumably may not exist yet, but its parent folder is assumed to exist.
103: * @throws IOException parent directory of project file does not exist.
104: */
105: public ProjectProfile(File f) throws IOException {
106: _projectFile = f;
107: _projectRoot = _projectFile.getParentFile();
108: if (!_projectRoot.exists())
109: throw new IOException("Parent directory of project root "
110: + _projectRoot + " does not exist");
111: }
112:
113: /* Public getters */
114:
115: /** @return an array of the source files in this project. */
116: public DocFile[] getSourceFiles() {
117: return _sourceFiles.toArray(new DocFile[_sourceFiles.size()]);
118: }
119:
120: /** @return an array full of all the aux files (project outside source tree) in this project. */
121: public DocFile[] getAuxiliaryFiles() {
122: return _auxFiles.toArray(new DocFile[_auxFiles.size()]);
123: }
124:
125: /** @return project file. */
126: public File getProjectFile() {
127: return _projectFile;
128: }
129:
130: /** @return the build directory stored in this project file */
131: public File getBuildDirectory() {
132: return _buildDir;
133: }
134:
135: /** @return the working directory stored in this project profile */
136: public File getWorkingDirectory() {
137: return _workDir;
138: }
139:
140: /** @return an array of path strings correspond to which folders in the tree should not be shown. Any paths not in
141: * this list will be expanded when the project is opened.
142: */
143: public String[] getCollapsedPaths() {
144: return _collapsedPaths.toArray(new String[_collapsedPaths
145: .size()]);
146: }
147:
148: /** @return an array full of all the classpath path elements in the classpath for this project file */
149: public Iterable<File> getClassPaths() {
150: return _classPathFiles;
151: }
152:
153: /** @return the name of the file that holds the Jar main class associated with this project */
154: public File getMainClass() {
155: return _mainClass;
156: }
157:
158: /** @return the project root directory which must exist. */
159: public File getProjectRoot() {
160: return _projectRoot;
161: }
162:
163: /** @return the output file used in the "Create Jar" dialog. */
164: public File getCreateJarFile() {
165: return _createJarFile;
166: }
167:
168: /** @return the output file used in the "Create Jar" dialog. */
169: public int getCreateJarFlags() {
170: return _createJarFlags;
171: }
172:
173: /** @return an array of the bookmarks in this project. */
174: public DocumentRegion[] getBookmarks() {
175: return _bookmarks
176: .toArray(new DocumentRegion[_bookmarks.size()]);
177: }
178:
179: /** @return an array of the breakpoints in this project. */
180: public DebugBreakpointData[] getBreakpoints() {
181: return _breakpoints
182: .toArray(new DebugBreakpointData[_breakpoints.size()]);
183: }
184:
185: /** @return an array of the watches in this project. */
186: public DebugWatchData[] getWatches() {
187: return _watches.toArray(new DebugWatchData[_watches.size()]);
188: }
189:
190: /** Public setters, modifiers */
191:
192: public void addSourceFile(DocFile df) {
193: _sourceFiles.add(df);
194: }
195:
196: public void addSourceFile(DocumentInfoGetter getter) {
197: if (!getter.isUntitled()) {
198: try {
199: addSourceFile(docFileFromGetter(getter));
200: } catch (IOException e) {
201: throw new UnexpectedException(e);
202: }
203: }
204: }
205:
206: public void addAuxiliaryFile(DocFile df) {
207: _auxFiles.add(df);
208: }
209:
210: public void addAuxiliaryFile(DocumentInfoGetter getter) {
211: if (!getter.isUntitled()) {
212: try {
213: addAuxiliaryFile(docFileFromGetter(getter));
214: } catch (IOException e) {
215: throw new UnexpectedException(e);
216: }
217: }
218: }
219:
220: public void addClassPathFile(File cp) {
221: if (cp != null)
222: _classPathFiles.add(cp);
223: }
224:
225: public void addCollapsedPath(String cp) {
226: if (cp != null)
227: _collapsedPaths.add(cp);
228: }
229:
230: public void setBuildDirectory(File dir) {
231: // System.err.println("setBuildDirectory(" + dir + ") called");
232: // removed call to validate to allow build directory that doesn't exist:
233: // it will be created when necessary
234: _buildDir = dir; // FileOps.validate(dir);
235: // System.err.println("Vaidated form is: " + _buildDir);
236: }
237:
238: public void setWorkingDirectory(File dir) {
239: _workDir = FileOps.validate(dir);
240: }
241:
242: public void setMainClass(File main) {
243: _mainClass = main;
244: }
245:
246: public void setSourceFiles(List<DocFile> sf) {
247: _sourceFiles = new ArrayList<DocFile>(sf);
248: }
249:
250: public void setClassPaths(Iterable<? extends File> cpf) {
251: _classPathFiles = new ArrayList<File>();
252: for (File f : cpf) {
253: _classPathFiles.add(f);
254: }
255: }
256:
257: public void setCollapsedPaths(List<String> cp) {
258: _collapsedPaths = new ArrayList<String>(cp);
259: }
260:
261: public void setAuxiliaryFiles(List<DocFile> af) {
262: _auxFiles = new ArrayList<DocFile>(af);
263: }
264:
265: /** Assumes that root.getParentFile != null */
266: public void setProjectRoot(File root) {
267: _projectRoot = root;
268: assert root.getParentFile() != null;
269: }
270:
271: public void setCreateJarFile(File createJarFile) {
272: _createJarFile = createJarFile;
273: }
274:
275: public void setCreateJarFlags(int createJarFlags) {
276: _createJarFlags = createJarFlags;
277: }
278:
279: public void setBookmarks(List<? extends DocumentRegion> bms) {
280: _bookmarks = new ArrayList<DocumentRegion>(bms);
281: }
282:
283: public void setBreakpoints(List<? extends DebugBreakpointData> bps) {
284: _breakpoints = new ArrayList<DebugBreakpointData>(bps);
285: }
286:
287: public void setWatches(List<? extends DebugWatchData> ws) {
288: _watches = new ArrayList<DebugWatchData>(ws);
289: }
290:
291: /** This method writes what information has been passed to this builder so far to disk in s-expression format. */
292: public void write() throws IOException {
293: FileWriter fw = new FileWriter(_projectFile);
294:
295: // write opening comment line
296: fw.write(";; DrJava project file, written by build "
297: + Version.getBuildTimeString());
298: fw.write("\n;; files in the source tree are relative to: "
299: + _projectRoot.getCanonicalPath());
300: fw
301: .write("\n;; other files with relative paths are rooted at (the parent of) this project file");
302:
303: // write the project root
304: /* In the new project file form, this property has been renamed "proj-root-and-base" (instead of "proj-root") to
305: * indicate that the project root now serves as the base for source file path names. */
306: if (_projectRoot != null) {
307: fw.write("\n(proj-root-and-base");
308: // Utilities.show("Writing project root = " + _projRoot);
309: fw.write("\n"
310: + encodeFileRelative(_projectRoot, " ",
311: _projectFile));
312: fw.write(")");
313: } else
314: fw.write("\n;; no project root; should never happen");
315:
316: // write source files
317: /* This property has been renamed "source-files" (instead of "source") so that old versions of DrJava will not
318: * recognize it. In the new project file format, source files are relative to the project root, not the parent
319: * of the project file. */
320: if (!_sourceFiles.isEmpty()) {
321: fw.write("\n(source-files");
322: DocFile active = null;
323: for (DocFile df : _sourceFiles) {
324: if (df.isActive()) {
325: active = df;
326: fw.write("\n" + encodeDocFileRelative(df, " "));
327: break; //Assert that there is only one active document in the project
328: }
329: }
330: for (DocFile df : _sourceFiles) {
331: if (df != active)
332: fw.write("\n" + encodeDocFileRelative(df, " "));
333: }
334: fw.write(")"); // close the source expression
335: } else
336: fw.write("\n;; no source files");
337:
338: // write aux files
339: if (!_auxFiles.isEmpty()) {
340: fw.write("\n(auxiliary");
341: for (DocFile df : _auxFiles) {
342: fw.write("\n" + encodeDocFileAbsolute(df, " "));
343: }
344: fw.write(")"); // close the auxiliary expression
345: } else
346: fw.write("\n;; no aux files");
347:
348: // write collapsed paths
349: if (!_collapsedPaths.isEmpty()) {
350: fw.write("\n(collapsed");
351: for (String s : _collapsedPaths) {
352: fw.write("\n (path " + convertToLiteral(s) + ")");
353: }
354: fw.write(")"); // close the collapsed expression
355: } else
356: fw.write("\n;; no collapsed branches");
357:
358: // write classpaths
359: if (!_classPathFiles.isEmpty()) {
360: fw.write("\n(classpaths");
361: for (File f : _classPathFiles) {
362: fw.write("\n" + encodeFileAbsolute(f, " "));
363: }
364: fw.write(")"); // close the classpaths expression
365: } else
366: fw.write("\n;; no classpaths files");
367:
368: // write the build directory
369: if (_buildDir != null && _buildDir.getPath() != "") {
370: fw.write("\n(build-dir");
371: fw
372: .write("\n"
373: + encodeFileRelative(_buildDir, " ",
374: _projectFile));
375: fw.write(")");
376: } else
377: fw.write("\n;; no build directory");
378:
379: // write the working directory
380: if (_workDir != null && _workDir.getPath() != "") {
381: fw.write("\n(work-dir");
382: fw.write("\n"
383: + encodeFileRelative(_workDir, " ", _projectFile));
384: fw.write(")");
385: } else
386: fw.write("\n;; no working directory");
387:
388: // write the main class
389: if (_mainClass != null) {
390: fw.write("\n;; rooted at the (parent of the) project file");
391: fw.write("\n(main-class");
392: fw
393: .write("\n"
394: + encodeFileRelative(_mainClass, " ",
395: _projectFile));
396: fw.write(")");
397: } else
398: fw.write("\n;; no main class");
399:
400: // // write the create jar file
401: // if (_createJarFile != null) {
402: // fw.write("\n(create-jar-file");
403: // fw.write("\n" + encodeFile(_createJarFile, " ", true));
404: // fw.write(")");
405: // }
406: // else fw.write("\n;; no create jar file");
407: //
408: // // write the create jar flags
409: // if (_createJarFlags != 0) {
410: // fw.write("\n(create-jar-flags " + _createJarFlags + ")");
411: // }
412: // else fw.write("\n;; no create jar flags");
413:
414: // write breakpoints
415: if (!_breakpoints.isEmpty()) {
416: fw.write("\n(breakpoints");
417: for (DebugBreakpointData bp : _breakpoints) {
418: fw.write("\n" + encodeBreakpointRelative(bp, " "));
419: }
420: fw.write(")"); // close the breakpoints expression
421: } else
422: fw.write("\n;; no breakpoints");
423:
424: // write watches
425: if (!_watches.isEmpty()) {
426: fw.write("\n(watches");
427: for (DebugWatchData w : _watches) {
428: fw.write("\n" + encodeWatch(w, " "));
429: }
430: fw.write(")"); // close the watches expression
431: } else
432: fw.write("\n;; no watches");
433:
434: // write bookmarks
435: if (!_bookmarks.isEmpty()) {
436: fw.write("\n(bookmarks");
437: for (DocumentRegion bm : _bookmarks) {
438: fw.write("\n" + encodeBookmarkRelative(bm, " "));
439: }
440: fw.write(")"); // close the bookmarks expression
441: } else
442: fw.write("\n;; no bookmarks");
443:
444: fw.close();
445: }
446:
447: /* Private Methods */
448:
449: /** @param getter The getter that can get all the info needed to make the document file
450: * @return the document that contains the information retrieved from the getter
451: */
452: private DocFile docFileFromGetter(DocumentInfoGetter g)
453: throws IOException {
454: return new DocFile(g.getFile().getCanonicalPath(), g
455: .getSelection(), g.getScroll(), g.isActive(), g
456: .getPackage());
457: }
458:
459: /** This encodes a normal file relative to File base. None of the special tags are added.
460: * @param f the file to encode
461: * @param prefix the indent level to place the s-expression at
462: * @param relative whether this file should be made relative to the project path
463: * @return the s-expression syntax to describe the given file.
464: */
465: private String encodeFileRelative(File f, String prefix, File base)
466: throws IOException {
467: String path = FileOps.makeRelativeTo(f, base).getPath();
468: path = replace(path, File.separator, "/");
469: return prefix + "(file (name " + convertToLiteral(path) + "))";
470: }
471:
472: /** This encodes a normal file relative to _projectRoot. None of the special tags are added. */
473: private String encodeFileRelative(File f, String prefix)
474: throws IOException {
475: return encodeFileRelative(f, prefix, _projectRoot);
476: }
477:
478: /** This encodes a normal file with its canonical path. None of the special tags are added.
479: * @param f the file to encode
480: * @param prefix the indent level to place the s-expression at
481: * @return the s-expression syntax to describe the given file.
482: */
483: private String encodeFileAbsolute(File f, String prefix)
484: throws IOException {
485: String path = f.getCanonicalPath();
486: path = replace(path, File.separator, "/");
487: return prefix + "(file (name " + convertToLiteral(path) + "))";
488: }
489:
490: /** This encodes a docfile, adding all the special tags that store document-specific information.
491: * @param df the doc file to encode
492: * @param prefix the indent level to place the s-expression at
493: * @param relative whether this file should be made relative to _projectRoot
494: * @return the s-expression syntax to describe the given docfile.
495: */
496: private String encodeDocFile(DocFile df, String prefix,
497: boolean relative) throws IOException {
498: String ret = "";
499: String path;
500: if (relative)
501: path = FileOps.makeRelativeTo(df, _projectRoot).getPath();
502: else
503: path = IOUtil.attemptCanonicalFile(df).getPath();
504:
505: path = replace(path, File.separator, "/");
506: ret += prefix + "(file (name " + convertToLiteral(path) + ")";
507:
508: Pair<Integer, Integer> p1 = df.getSelection();
509: Pair<Integer, Integer> p2 = df.getScroll();
510: //boolean active = false; //df.isActive();
511: long modDate = df.lastModified();
512: // Add prefix to the next line if any tags exist
513: if (p1 != null || p2 != null /*|| active */)
514: ret += "\n" + prefix + " ";
515:
516: // The next three tags go on the same line (if they exist)
517: if (p1 != null)
518: ret += "(select " + p1.first() + " " + p1.second() + ")";
519:
520: if (p2 != null)
521: ret += "(scroll " + p2.first() + " " + p2.second() + ")";
522:
523: if (modDate > 0) {
524: String s = MOD_DATE_FORMAT.format(new Date(modDate));
525: ret += "(mod-date " + convertToLiteral(s) + ")";
526: }
527:
528: //if (active) ret += "(active)"; //Active document is first on list
529:
530: // the next tag goes on the next line if at all
531: String pack = df.getPackage();
532: if (pack != null) {
533: ret += "\n" + prefix + " "; // add prefix
534: ret += "(package " + convertToLiteral(pack) + ")";
535: }
536:
537: ret += ")"; // close the file expression
538:
539: return ret;
540: }
541:
542: /** Encodes a doc file relative to _projectRoot.
543: * @param df the DocFile to encode
544: * @param prefix the indent level
545: */
546: private String encodeDocFileRelative(DocFile df, String prefix)
547: throws IOException {
548: return encodeDocFile(df, prefix, true);
549: }
550:
551: private String encodeDocFileAbsolute(DocFile df, String prefix)
552: throws IOException {
553: return encodeDocFile(df, prefix, false);
554: }
555:
556: /** This encodes a breakpoint relative to _projectRoot.
557: * @param bp the breakpoint to encode
558: * @param prefix the indent level to place the s-expression at
559: * @return the s-expression syntax to describe the given breakpoint.
560: */
561: private String encodeBreakpointRelative(DebugBreakpointData bp,
562: String prefix) throws IOException {
563: String ret = "";
564: String path = FileOps
565: .makeRelativeTo(bp.getFile(), _projectRoot).getPath();
566:
567: path = replace(path, File.separator, "/");
568: ret += prefix + "(breakpoint (name " + convertToLiteral(path)
569: + ")";
570:
571: int offset = bp.getOffset();
572: int lineNumber = bp.getLineNumber();
573: ret += "\n" + prefix + " ";
574: ret += "(offset " + offset + ")";
575: ret += "(line " + lineNumber + ")";
576: if (bp.isEnabled())
577: ret += "(enabled)";
578: ret += ")"; // close the breakpoint expression
579:
580: return ret;
581: }
582:
583: /** This encodes a watch.
584: * @param w the watch to encode
585: * @param prefix the indent level to place the s-expression at
586: * @return the s-expression syntax to describe the given watch.
587: */
588: private String encodeWatch(DebugWatchData w, String prefix)
589: throws IOException {
590: String ret = "";
591:
592: ret += prefix + "(watch " + convertToLiteral(w.getName()) + ")";
593:
594: return ret;
595: }
596:
597: /** This encodes a bookmark relative to _projectRoot.
598: * @param bm the bookmark to encode
599: * @param prefix the indent level to place the s-expression at
600: * @return the s-expression syntax to describe the given breakpoint.
601: */
602: private String encodeBookmarkRelative(DocumentRegion bp,
603: String prefix) throws IOException {
604: String ret = "";
605: String path = FileOps.makeRelativeTo(
606: bp.getDocument().getFile(), _projectRoot).getPath();
607:
608: path = replace(path, File.separator, "/");
609: ret += prefix + "(bookmark (name " + convertToLiteral(path)
610: + ")";
611:
612: int startOffset = bp.getStartOffset();
613: int endOffset = bp.getEndOffset();
614: ret += "\n" + prefix + " ";
615: ret += "(start " + startOffset + ")";
616: ret += "(end " + endOffset + ")";
617: ret += ")"; // close the bookmarks expression
618:
619: return ret;
620: }
621: }
|