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.io.File;
040: import java.io.IOException;
041: import java.io.FileNotFoundException;
042: import java.util.List;
043: import java.util.ArrayList;
044: import java.util.Date;
045: import java.text.SimpleDateFormat;
046:
047: import edu.rice.cs.drjava.config.FileOption;
048: import edu.rice.cs.plt.tuple.Pair;
049: import edu.rice.cs.util.sexp.*;
050: import edu.rice.cs.drjava.model.DocumentRegion;
051: import edu.rice.cs.drjava.model.SimpleDocumentRegion;
052: import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
053: import edu.rice.cs.drjava.model.debug.DebugWatchData;
054: import edu.rice.cs.drjava.model.debug.DebugBreakpointData;
055: import edu.rice.cs.drjava.model.debug.DebugException;
056:
057: /** This parser uses the s-expression parser defined in the util pacakge. The SExp tree given by the parser is
058: * interpreted into a ProjectFileIR that is given to the user. This class must also deal with different
059: * versions of the project file.
060: *
061: * <p> If at some point new information is to be stored in the project file, the following places in the code that need to
062: * changed: <menu> <li> If the new information pertains to a document, the DocFile class should be augmented to
063: * store the new info. <li> The interface for the DocumentInfoGetter should be expanded to allow for the new
064: * data to be retrieved. <li> Add a new clause to the else-if ladder in the FilePropertyVisitor. <li>
065: * Add the new information to the DocFile form the DocumentInfoGetter in the ProjectFileBuilder's
066: * addSourceDocument method.</p>
067: *
068: * <p> If the change is at the top level, you must modify the evaluateExpression method in this parser and add the
069: * corresponding methods to the ProjectFileIR, ProjectFileIRImpl, and ProjectFileBuilder</p>
070: */
071: public class ProjectFileParser {
072: /** Singleton instance of ProjectFileParser */
073: public static final ProjectFileParser ONLY = new ProjectFileParser();
074:
075: private File _projectFile;
076: private String _parent;
077: private String _srcFileBase;
078:
079: BreakpointListVisitor breakpointListVisitor = new BreakpointListVisitor();
080: BookmarkListVisitor bookmarkListVisitor = new BookmarkListVisitor();
081:
082: private ProjectFileParser() {
083: }
084:
085: /* methods */
086:
087: /** @param projFile the file to parse
088: * @return the project file IR
089: */
090: public ProjectFileIR parse(File projFile) throws IOException,
091: FileNotFoundException, MalformedProjectFileException {
092:
093: _projectFile = projFile;
094: _parent = projFile.getParent();
095: _srcFileBase = _parent; // oldest legacy file format may omit proj-root or proj-root-and-base node
096: // System.err.println("Parsing project file " + projFile + " with parent " + _parent);
097:
098: List<SEList> forest = null;
099: try {
100: forest = SExpParser.parse(projFile);
101: } catch (SExpParseException e) {
102: throw new MalformedProjectFileException("Parse Error: "
103: + e.getMessage());
104: }
105:
106: ProjectFileIR pfir = new ProjectProfile(projFile);
107:
108: try {
109: for (SEList exp : forest)
110: evaluateExpression(exp, pfir, new DocFileListVisitor(
111: _parent));
112: } catch (PrivateProjectException e) {
113: throw new MalformedProjectFileException("Parse Error: "
114: + e.getMessage());
115: }
116:
117: // System.err.println("Parsed buildDir is " + pfir.getBuildDirectory());
118:
119: return pfir;
120: }
121:
122: /** Given a top-level s-expression, this method checks the name of the node and configures the given pfir
123: * appropriately. If the expression is empty, it is ignored.
124: * @param e the top-level s-expression to check
125: * @param pfir the ProjectFileIR to update
126: */
127: private void evaluateExpression(SEList e, ProjectFileIR pfir,
128: DocFileListVisitor flv) throws IOException {
129: if (e == Empty.ONLY)
130: return;
131: Cons exp = (Cons) e; // If it's not empty, it's a cons
132:
133: String name = exp.accept(NameVisitor.ONLY);
134: if ((name.compareToIgnoreCase("source") == 0)
135: || (name.compareToIgnoreCase("source-files") == 0)) {
136: List<DocFile> dfList = exp.getRest().accept(
137: new DocFileListVisitor(_srcFileBase));
138: pfir.setSourceFiles(dfList);
139: } else if (name.compareToIgnoreCase("proj-root") == 0) { // legacy node form; all paths relative to project file
140: List<DocFile> fList = exp.getRest().accept(flv);
141: if (fList.size() > 1)
142: throw new PrivateProjectException(
143: "Cannot have multiple source roots");
144: else if (fList.size() == 0)
145: pfir.setProjectRoot(null); // can this ever happen?
146: pfir.setProjectRoot(fList.get(0));
147: } else if (name.compareToIgnoreCase("proj-root-and-base") == 0) { // source file paths are relative to project root
148: List<DocFile> fList = exp.getRest().accept(flv);
149: if (fList.size() > 1)
150: throw new PrivateProjectException(
151: "Cannot have multiple source roots");
152: File root = fList.get(0);
153: if (!root.exists())
154: throw new IOException("Project root " + root
155: + " no longer exists");
156: pfir.setProjectRoot(root);
157: _srcFileBase = root.getCanonicalPath();
158: } else if (name.compareToIgnoreCase("auxiliary") == 0) {
159: List<DocFile> dfList = exp.getRest().accept(flv);
160: pfir.setAuxiliaryFiles(dfList);
161: } else if (name.compareToIgnoreCase("collapsed") == 0) {
162: List<String> sList = exp.getRest().accept(
163: PathListVisitor.ONLY);
164: pfir.setCollapsedPaths(sList);
165: } else if (name.compareToIgnoreCase("build-dir") == 0) {
166: List<DocFile> fList = exp.getRest().accept(flv);
167: // System.err.println("BuildDir fList = " + fList);
168: if (fList.size() > 1)
169: throw new PrivateProjectException(
170: "Cannot have multiple build directories");
171: else if (fList.size() == 0)
172: pfir.setBuildDirectory(null);
173: else
174: pfir.setBuildDirectory(fList.get(0));
175: } else if (name.compareToIgnoreCase("work-dir") == 0) {
176: List<DocFile> fList = exp.getRest().accept(flv);
177: if (fList.size() > 1)
178: throw new PrivateProjectException(
179: "Cannot have multiple working directories");
180: else if (fList.size() == 0)
181: pfir.setWorkingDirectory(null);
182: else
183: pfir.setWorkingDirectory(fList.get(0));
184: } else if (name.compareToIgnoreCase("classpaths") == 0) {
185: List<DocFile> fList = exp.getRest().accept(flv);
186: pfir.setClassPaths(fList);
187: } else if (name.compareToIgnoreCase("main-class") == 0) {
188: List<DocFile> fList = exp.getRest().accept(flv);
189: if (fList.size() > 1)
190: throw new PrivateProjectException(
191: "Cannot have multiple main classes");
192: else if (fList.size() == 0)
193: pfir.setMainClass(null);
194: else
195: pfir.setMainClass(fList.get(0));
196: }
197: // else if (name.compareToIgnoreCase("create-jar-file") == 0) {
198: // List<File> fList = exp.getRest().accept(fileListVisitor);
199: // if (fList.size() > 1) throw new PrivateProjectException("Cannot have more than one \"create jar\" file");
200: // else if (fList.size() == 0) pfir.setCreateJarFile(null);
201: // else pfir.setCreateJarFile(fList.get(0));
202: // }
203: // else if (name.compareToIgnoreCase("create-jar-flags") == 0) {
204: // Integer i = exp.getRest().accept(NumberVisitor.ONLY);
205: // pfir.setCreateJarFlags(i);
206: // }
207: else if (name.compareToIgnoreCase("breakpoints") == 0) {
208: List<DebugBreakpointData> bpList = exp.getRest().accept(
209: breakpointListVisitor);
210: pfir.setBreakpoints(bpList);
211: } else if (name.compareToIgnoreCase("watches") == 0) {
212: List<DebugWatchData> sList = exp.getRest().accept(
213: WatchListVisitor.ONLY);
214: pfir.setWatches(sList);
215: } else if (name.compareToIgnoreCase("bookmarks") == 0) {
216: List<DocumentRegion> bmList = exp.getRest().accept(
217: bookmarkListVisitor);
218: pfir.setBookmarks(bmList);
219: }
220: }
221:
222: /** Parses out the labeled node (a non-empty list) into a DocFile. The node must have the "file" label on it.
223: * @param s the non-empty list expression
224: * @return the DocFile described by this s-expression
225: */
226: DocFile parseFile(SExp s, String pathRoot) {
227: String name = s.accept(NameVisitor.ONLY);
228: if (name.compareToIgnoreCase("file") != 0)
229: throw new PrivateProjectException(
230: "Expected a file tag, found: " + name);
231: if (!(s instanceof Cons))
232: throw new PrivateProjectException(
233: "Expected a labeled node, found a label: " + name);
234: SEList c = ((Cons) s).getRest(); // get parameter list
235:
236: DocFilePropertyVisitor v = new DocFilePropertyVisitor(pathRoot);
237: return c.accept(v);
238: }
239:
240: private String parseFileName(SExp s) {
241: if (s instanceof Cons) {
242: SEList l = ((Cons) s).getRest();
243: if (l == Empty.ONLY)
244: throw new PrivateProjectException(
245: "expected filename, but nothing found");
246: else {
247: String name = l.accept(NameVisitor.ONLY);
248: name = edu.rice.cs.util.StringOps.replace(name, "\\",
249: "/");
250: return name;
251: }
252: } else
253: throw new PrivateProjectException(
254: "expected name tag, found string");
255: }
256:
257: private int parseInt(SExp s) {
258: if (s instanceof Cons) {
259: SEList l = ((Cons) s).getRest();
260: if (l == Empty.ONLY)
261: throw new PrivateProjectException(
262: "expected integer, but nothing found");
263: else {
264: int i = l.accept(NumberVisitor.ONLY);
265: return i;
266: }
267: } else
268: throw new PrivateProjectException(
269: "expected name tag, found string");
270: }
271:
272: private Pair<Integer, Integer> parseIntPair(SExp s) {
273: int row;
274: int col;
275:
276: /* we're getting in a "(select # #)" */
277: if (!(s instanceof Cons)) {
278: throw new PrivateProjectException(
279: "expected name tag, found string");
280: }
281:
282: // get rid of "select"
283: final List<Integer> intList = new ArrayList<Integer>();
284: SEList l = ((Cons) s).getRest();
285: List<Integer> li = l.accept(new SExpVisitor<List<Integer>>() {
286: public List<Integer> forEmpty(Empty e) {
287: return intList;
288: }
289:
290: public List<Integer> forCons(Cons c) {
291: c.getFirst().accept(this );
292: return c.getRest().accept(this );
293: }
294:
295: public List<Integer> forBoolAtom(BoolAtom b) {
296: throw new PrivateProjectException(
297: "unexpected boolean found, int expected");
298: }
299:
300: public List<Integer> forNumberAtom(NumberAtom n) {
301: intList.add(new Integer(n.intValue()));
302: return intList;
303: }
304:
305: public List<Integer> forTextAtom(TextAtom t) {
306: throw new PrivateProjectException(
307: "unexpected string found where number expected: "
308: + t.getText());
309: }
310:
311: });
312:
313: if (li.size() == 2)
314: return new Pair<Integer, Integer>(li.get(0), li.get(1));
315: else
316: throw new PrivateProjectException(
317: "expected a list of 2 ints for select, found list of size "
318: + li.size());
319: }
320:
321: /** Takes input of form "(str str)" and returns the second string. */
322: private String parseStringNode(SExp n) {
323: if (n instanceof Cons)
324: return ((Cons) n).getRest().accept(NameVisitor.ONLY);
325: else
326: throw new PrivateProjectException(
327: "List expected, but found text instead");
328: }
329:
330: /* nested/inner classes */
331:
332: /** Parses out a list of file nodes. */
333: private static class DocFileListVisitor implements
334: SEListVisitor<List<DocFile>> {
335: /** Base directory for relative paths */
336: private String _base;
337:
338: DocFileListVisitor(String base) {
339: _base = base;
340: }
341:
342: public List<DocFile> forEmpty(Empty e) {
343: return new ArrayList<DocFile>();
344: }
345:
346: public List<DocFile> forCons(Cons c) {
347: List<DocFile> list = c.getRest().accept(this );
348: DocFile tmp = ProjectFileParser.ONLY.parseFile(
349: c.getFirst(), _base);
350: list.add(0, tmp); // add to the end
351: return list;
352: }
353: };
354:
355: /** Traverses the list of expressions found after "file" tag and returns the DocFile described by those properties. */
356: private static class DocFilePropertyVisitor implements
357: SEListVisitor<DocFile> {
358: private String fname = "";
359: private Pair<Integer, Integer> select = new Pair<Integer, Integer>(
360: new Integer(0), new Integer(0));
361: private Pair<Integer, Integer> scroll = new Pair<Integer, Integer>(
362: new Integer(0), new Integer(0));
363: private boolean active = false;
364: private String pack = "";
365: private Date modDate = null;
366:
367: private String pathRoot;
368:
369: public DocFilePropertyVisitor(String pr) {
370: pathRoot = pr;
371: }
372:
373: public DocFile forCons(Cons c) {
374: String name = c.getFirst().accept(NameVisitor.ONLY);
375: if (name.compareToIgnoreCase("name") == 0) {
376: fname = ProjectFileParser.ONLY.parseFileName(c
377: .getFirst());
378: } else if (name.compareToIgnoreCase("select") == 0) {
379: select = ProjectFileParser.ONLY.parseIntPair(c
380: .getFirst());
381: } else if (name.compareToIgnoreCase("scroll") == 0) {
382: scroll = ProjectFileParser.ONLY.parseIntPair(c
383: .getFirst());
384: } else if (name.compareToIgnoreCase("active") == 0) {
385: active = true;
386: } else if (name.compareToIgnoreCase("package") == 0) {
387: pack = ProjectFileParser.ONLY.parseStringNode(c
388: .getFirst());
389: } else if (name.compareToIgnoreCase("mod-date") == 0) {
390: String tmp = ProjectFileParser.ONLY.parseStringNode(c
391: .getFirst());
392: try {
393: //attemp parsing in default locale
394: modDate = ProjectProfile.MOD_DATE_FORMAT.parse(tmp);
395: } catch (java.text.ParseException e1) {
396: //parsing in default locale failed
397: try {
398: //attempt parsing in current locale
399: modDate = new SimpleDateFormat(
400: ProjectProfile.MOD_DATE_FORMAT_STRING)
401: .parse(tmp);
402: } catch (java.text.ParseException e2) {
403: //both parsings failed
404: throw new PrivateProjectException(
405: "Bad mod-date: " + e2.getMessage());
406: }
407: }
408: }
409:
410: return c.getRest().accept(this );
411: }
412:
413: public DocFile forEmpty(Empty c) {
414: if (pathRoot == null || new File(fname).isAbsolute()) {
415: return new DocFile(fname, select, scroll, active, pack);
416: } else {
417: DocFile f = new DocFile(pathRoot, fname, select,
418: scroll, active, pack);
419: if (modDate != null)
420: f.setSavedModDate(modDate.getTime());
421: return f;
422: }
423: }
424: }
425:
426: /** Parses out a list of path nodes into a list of Strings. */
427: private static class PathListVisitor implements
428: SEListVisitor<List<String>> {
429: public static final PathListVisitor ONLY = new PathListVisitor();
430:
431: private PathListVisitor() {
432: }
433:
434: public List<String> forEmpty(Empty e) {
435: return new ArrayList<String>();
436: }
437:
438: public List<String> forCons(Cons c) {
439: List<String> list = c.getRest().accept(this );
440: SExp first = c.getFirst();
441: String name = first.accept(NameVisitor.ONLY);
442: if (name.compareToIgnoreCase("path") == 0) {
443: String tmp = ProjectFileParser.ONLY.parseStringNode(c
444: .getFirst());
445: list.add(0, tmp); // add to the end
446: }
447: return list;
448: }
449: };
450:
451: /** Retrieves the name of a node. The node should either be a list with its first element being a text atom,
452: * or a text atom itself.
453: */
454: private static class NameVisitor implements SExpVisitor<String> {
455: public static final NameVisitor ONLY = new NameVisitor();
456:
457: private NameVisitor() {
458: }
459:
460: public String forEmpty(Empty e) {
461: throw new PrivateProjectException(
462: "Found an empty node, expected a labeled node");
463: }
464:
465: public String forCons(Cons c) {
466: return c.getFirst().accept(this );
467: }
468:
469: public String forBoolAtom(BoolAtom b) {
470: throw new PrivateProjectException(
471: "Found a boolean, expected a label");
472: }
473:
474: public String forNumberAtom(NumberAtom n) {
475: throw new PrivateProjectException(
476: "Found a number, expected a label");
477: }
478:
479: public String forTextAtom(TextAtom t) {
480: return t.getText();
481: }
482: };
483:
484: /** Retrieves the number of a node. The node should either be a list with its first element being a number atom,
485: * or a number atom itself.
486: */
487: private static class NumberVisitor implements SExpVisitor<Integer> {
488: public static final NumberVisitor ONLY = new NumberVisitor();
489:
490: private NumberVisitor() {
491: }
492:
493: public Integer forEmpty(Empty e) {
494: throw new PrivateProjectException(
495: "Found an empty node, expected an integer");
496: }
497:
498: public Integer forCons(Cons c) {
499: return c.getFirst().accept(this );
500: }
501:
502: public Integer forBoolAtom(BoolAtom b) {
503: throw new PrivateProjectException(
504: "Found a boolean, expected an integer");
505: }
506:
507: public Integer forNumberAtom(NumberAtom n) {
508: return n.intValue();
509: }
510:
511: public Integer forTextAtom(TextAtom t) {
512: throw new PrivateProjectException("Found a string '" + t
513: + "', expected an integer");
514: }
515: };
516:
517: /** Parses out a list of watch names into a list of watches. */
518: private static class WatchListVisitor implements
519: SEListVisitor<List<DebugWatchData>> {
520: public static final WatchListVisitor ONLY = new WatchListVisitor();
521:
522: private WatchListVisitor() {
523: }
524:
525: public List<DebugWatchData> forEmpty(Empty e) {
526: return new ArrayList<DebugWatchData>();
527: }
528:
529: public List<DebugWatchData> forCons(Cons c) {
530: List<DebugWatchData> list = c.getRest().accept(this );
531: SExp first = c.getFirst();
532: String name = first.accept(NameVisitor.ONLY);
533: if (name.compareToIgnoreCase("watch") == 0) {
534: String tmp = ProjectFileParser.ONLY.parseStringNode(c
535: .getFirst());
536: list.add(0, new DebugWatchData(tmp)); // add to the end
537: }
538: return list;
539: }
540: };
541:
542: // === breakpoints ===
543:
544: /** Parses out a list of breakpoint nodes. */
545: private class BreakpointListVisitor implements
546: SEListVisitor<List<DebugBreakpointData>> {
547: public List<DebugBreakpointData> forEmpty(Empty e) {
548: return new ArrayList<DebugBreakpointData>();
549: }
550:
551: public List<DebugBreakpointData> forCons(Cons c) {
552: List<DebugBreakpointData> list = c.getRest().accept(this );
553: DebugBreakpointData tmp = ProjectFileParser.ONLY
554: .parseBreakpoint(c.getFirst(), _srcFileBase);
555: list.add(0, tmp); // add to the end
556: return list;
557: }
558: };
559:
560: /** Parses out the labeled node (a non-empty list) into a breakpoint. The node must have the "breakpoint" label on it.
561: * @param s the non-empty list expression
562: * @return the breakpoint described by this s-expression
563: */
564: DebugBreakpointData parseBreakpoint(SExp s, String pathRoot) {
565: String name = s.accept(NameVisitor.ONLY);
566: if (name.compareToIgnoreCase("breakpoint") != 0)
567: throw new PrivateProjectException(
568: "Expected a breakpoint tag, found: " + name);
569: if (!(s instanceof Cons))
570: throw new PrivateProjectException(
571: "Expected a labeled node, found a label: " + name);
572: SEList c = ((Cons) s).getRest(); // get parameter list
573:
574: BreakpointPropertyVisitor v = new BreakpointPropertyVisitor(
575: pathRoot);
576: return c.accept(v);
577: }
578:
579: /** Traverses the list of expressions found after "breakpoint" tag and returns the Breakpoint described by those properties. */
580: private static class BreakpointPropertyVisitor implements
581: SEListVisitor<DebugBreakpointData> {
582: private String fname = null;
583: private Integer offset = null;
584: private Integer lineNumber = null;
585: private boolean isEnabled = false;
586:
587: private String pathRoot;
588:
589: public BreakpointPropertyVisitor(String pr) {
590: pathRoot = pr;
591: }
592:
593: public DebugBreakpointData forCons(Cons c) {
594: String name = c.getFirst().accept(NameVisitor.ONLY);
595: if (name.compareToIgnoreCase("name") == 0) {
596: fname = ProjectFileParser.ONLY.parseFileName(c
597: .getFirst());
598: } else if (name.compareToIgnoreCase("offset") == 0) {
599: offset = ProjectFileParser.ONLY.parseInt(c.getFirst());
600: } else if (name.compareToIgnoreCase("line") == 0) {
601: lineNumber = ProjectFileParser.ONLY.parseInt(c
602: .getFirst());
603: } else if (name.compareToIgnoreCase("enabled") == 0) {
604: isEnabled = true;
605: }
606:
607: return c.getRest().accept(this );
608: }
609:
610: public DebugBreakpointData forEmpty(Empty c) {
611: if ((fname == null) || (offset == null)
612: || (lineNumber == null)) {
613: throw new PrivateProjectException(
614: "Breakpoint information incomplete, need name, offset and line tags");
615: }
616: if (pathRoot == null || new File(fname).isAbsolute()) {
617: final File f = new File(fname);
618: return new DebugBreakpointData() {
619: public File getFile() {
620: return f;
621: }
622:
623: public int getOffset() {
624: return offset;
625: }
626:
627: public int getLineNumber() {
628: return lineNumber;
629: }
630:
631: public boolean isEnabled() {
632: return isEnabled;
633: }
634: };
635: } else {
636: final File f = new File(pathRoot, fname);
637: return new DebugBreakpointData() {
638: public File getFile() {
639: return f;
640: }
641:
642: public int getOffset() {
643: return offset;
644: }
645:
646: public int getLineNumber() {
647: return lineNumber;
648: }
649:
650: public boolean isEnabled() {
651: return isEnabled;
652: }
653: };
654: }
655: }
656: }
657:
658: // === bookmarks ===
659:
660: /** Parses out a list of bookmark nodes. */
661: private class BookmarkListVisitor implements
662: SEListVisitor<List<DocumentRegion>> {
663: public List<DocumentRegion> forEmpty(Empty e) {
664: return new ArrayList<DocumentRegion>();
665: }
666:
667: public List<DocumentRegion> forCons(Cons c) {
668: List<DocumentRegion> list = c.getRest().accept(this );
669: DocumentRegion tmp = ProjectFileParser.ONLY.parseBookmark(c
670: .getFirst(), _srcFileBase);
671: list.add(0, tmp); // add to the end
672: return list;
673: }
674: };
675:
676: /** Parses out the labeled node (a non-empty list) into a bookmark. The node must have the "bookmark" label on it.
677: * @param s the non-empty list expression
678: * @return the bookmark described by this s-expression
679: */
680: DocumentRegion parseBookmark(SExp s, String pathRoot) {
681: String name = s.accept(NameVisitor.ONLY);
682: if (name.compareToIgnoreCase("bookmark") != 0)
683: throw new PrivateProjectException(
684: "Expected a bookmark tag, found: " + name);
685: if (!(s instanceof Cons))
686: throw new PrivateProjectException(
687: "Expected a labeled node, found a label: " + name);
688: SEList c = ((Cons) s).getRest(); // get parameter list
689:
690: BookmarkPropertyVisitor v = new BookmarkPropertyVisitor(
691: pathRoot);
692: return c.accept(v);
693: }
694:
695: /** Traverses the list of expressions found after "bookmark" tag and returns the DocumentRegion
696: * described by those properties. */
697: private static class BookmarkPropertyVisitor implements
698: SEListVisitor<DocumentRegion> {
699: private String fname = null;
700: private Integer startOffset = null;
701: private Integer endOffset = null;
702:
703: private String pathRoot;
704:
705: public BookmarkPropertyVisitor(String pr) {
706: pathRoot = pr;
707: }
708:
709: public DocumentRegion forCons(Cons c) {
710: String name = c.getFirst().accept(NameVisitor.ONLY);
711: if (name.compareToIgnoreCase("name") == 0) {
712: fname = ProjectFileParser.ONLY.parseFileName(c
713: .getFirst());
714: } else if (name.compareToIgnoreCase("start") == 0) {
715: startOffset = ProjectFileParser.ONLY.parseInt(c
716: .getFirst());
717: } else if (name.compareToIgnoreCase("end") == 0) {
718: endOffset = ProjectFileParser.ONLY.parseInt(c
719: .getFirst());
720: }
721:
722: return c.getRest().accept(this );
723: }
724:
725: public DocumentRegion forEmpty(Empty c) {
726: if ((fname == null) || (startOffset == null)
727: || (endOffset == null)) {
728: throw new PrivateProjectException(
729: "Bookmark information incomplete, need name, start offset and end offset");
730: }
731: File f;
732: if (pathRoot == null || new File(fname).isAbsolute()) {
733: f = new File(fname);
734: } else {
735: f = new File(pathRoot, fname);
736: }
737: return new SimpleDocumentRegion(null, f, startOffset,
738: endOffset);
739: }
740: }
741:
742: private static class PrivateProjectException extends
743: RuntimeException {
744: public PrivateProjectException(String message) {
745: super(message);
746: }
747: }
748: }
|