Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 574   Methods: 56
NCLOC: 401   Classes: 12
 
 Source file Conditionals Statements Methods TOTAL
ProjectFileParser.java 43.5% 48.6% 41.1% 46.2%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.project;
 38   
 39    import java.io.File;
 40    import java.io.IOException;
 41    import java.io.FileNotFoundException;
 42    import java.util.List;
 43    import java.util.ArrayList;
 44    import java.util.Date;
 45    import java.text.SimpleDateFormat;
 46   
 47    import edu.rice.cs.plt.tuple.Pair;
 48    import edu.rice.cs.util.FileOps;
 49    import edu.rice.cs.util.sexp.*;
 50    import edu.rice.cs.drjava.model.FileRegion;
 51    import edu.rice.cs.drjava.model.DummyDocumentRegion;
 52    import edu.rice.cs.drjava.model.debug.DebugWatchData;
 53    import edu.rice.cs.drjava.model.debug.DebugBreakpointData;
 54   
 55    /** This parser uses the s-expression parser defined in the util pacakge. The SExp tree given by the parser is
 56    * interpreted into a ProjectFileIR that is given to the user. This class must also deal with different
 57    * versions of the project file.
 58    *
 59    * <p> If at some point new information is to be stored in the project file, the following places in the code that need to
 60    * changed: <menu> <li> If the new information pertains to a document, the DocFile class should be augmented to
 61    * store the new info. <li> The interface for the DocumentInfoGetter should be expanded to allow for the new
 62    * data to be retrieved. <li> Add a new clause to the else-if ladder in the FilePropertyVisitor. <li>
 63    * Add the new information to the DocFile form the DocumentInfoGetter in the ProjectFileBuilder's
 64    * addSourceDocument method.</p>
 65    *
 66    * <p> If the change is at the top level, you must modify the evaluateExpression method in this parser and add the
 67    * corresponding methods to the ProjectFileIR, ProjectFileIRImpl, and ProjectFileBuilder</p>
 68    */
 69    public class ProjectFileParser extends ProjectFileParserFacade {
 70    /** Singleton instance of ProjectFileParser */
 71    public static final ProjectFileParser ONLY = new ProjectFileParser();
 72   
 73    private String _parent;
 74    private String _srcFileBase;
 75   
 76    BreakpointListVisitor breakpointListVisitor = new BreakpointListVisitor();
 77    BookmarkListVisitor bookmarkListVisitor = new BookmarkListVisitor();
 78   
 79  2 private ProjectFileParser() { _xmlProjectFile = false; }
 80   
 81    /** @param projFile the file to parse
 82    * @return the project file IR
 83    */
 84  8 public ProjectFileIR parse(File projFile) throws IOException, FileNotFoundException, MalformedProjectFileException {
 85   
 86  8 _projectFile = projFile;
 87  8 _parent = projFile.getParent();
 88  8 _srcFileBase = _parent; // oldest legacy file format may omit proj-root or proj-root-and-base node
 89    // System.err.println("Parsing project file " + projFile + " with parent " + _parent);
 90   
 91  8 List<SEList> forest = null;
 92  8 try { forest = SExpParser.parse(projFile); }
 93  0 catch(SExpParseException e) { throw new MalformedProjectFileException("Parse Error: " + e.getMessage()); }
 94   
 95  8 ProjectFileIR pfir = new ProjectProfile(projFile);
 96   
 97    //We don't store version information in .pjt files. Yet another reason to use the .drjava or .xml format.
 98  8 pfir.setDrJavaVersion("unknown");
 99   
 100  44 try { for (SEList exp : forest) evaluateExpression(exp, pfir, new DocFileListVisitor(_parent)); }
 101  0 catch(PrivateProjectException e) { throw new MalformedProjectFileException("Parse Error: " + e.getMessage()); }
 102   
 103    // System.err.println("Parsed buildDir is " + pfir.getBuildDirectory());
 104   
 105  8 return pfir;
 106    }
 107   
 108    /** Given a top-level s-expression, this method checks the name of the node and configures the given pfir
 109    * appropriately. If the expression is empty, it is ignored.
 110    * @param e the top-level s-expression to check
 111    * @param pfir the ProjectFileIR to update
 112    */
 113  44 private void evaluateExpression(SEList e, ProjectFileIR pfir, DocFileListVisitor flv) throws IOException {
 114  0 if (e == Empty.ONLY) return;
 115  44 Cons exp = (Cons) e; // If it's not empty, it's a cons
 116   
 117  44 String name = exp.accept(NameVisitor.ONLY);
 118  44 if ((name.compareToIgnoreCase("source") == 0) || (name.compareToIgnoreCase("source-files") == 0)) {
 119  8 List<DocFile> dfList = exp.getRest().accept(new DocFileListVisitor(_srcFileBase));
 120  8 pfir.setSourceFiles(dfList);
 121    }
 122  36 else if (name.compareToIgnoreCase("proj-root") == 0) { // legacy node form; all paths relative to project file
 123  2 List<DocFile> fList = exp.getRest().accept(flv);
 124  0 if (fList.size() > 1) throw new PrivateProjectException("Cannot have multiple source roots");
 125  0 else if (fList.size() == 0) throw new PrivateProjectException("Cannot have no source roots");
 126  2 pfir.setProjectRoot(fList.get(0));
 127    }
 128  34 else if (name.compareToIgnoreCase("proj-root-and-base") == 0) { // source file paths are relative to project root
 129  3 List<DocFile> fList = exp.getRest().accept(flv);
 130  0 if (fList.size() > 1) throw new PrivateProjectException("Cannot have multiple source roots");
 131  0 else if (fList.size() == 0) throw new PrivateProjectException("Cannot have no source roots");
 132  3 File root = fList.get(0);
 133  0 if (! root.exists()) throw new IOException("Project root " + root + " no longer exists");
 134  3 pfir.setProjectRoot(root);
 135  3 _srcFileBase = root.getCanonicalPath();
 136  31 }else if (name.compareToIgnoreCase("proj-manifest") == 0) {
 137  0 List<String> sList = exp.getRest().accept(PathListVisitor.ONLY);
 138  0 if(sList.size() > 1) throw new PrivateProjectException("Cannot have multiple manifests");
 139  0 if(sList.size() > 0)
 140  0 pfir.setCustomManifest(sList.get(0));
 141    }
 142  31 else if (name.compareToIgnoreCase("auxiliary") == 0) {
 143  5 List<DocFile> dfList = exp.getRest().accept(flv);
 144  5 pfir.setAuxiliaryFiles(dfList);
 145    }
 146  26 else if (name.compareToIgnoreCase("collapsed") == 0) {
 147  5 List<String> sList = exp.getRest().accept(PathListVisitor.ONLY);
 148  5 pfir.setCollapsedPaths(sList);
 149    }
 150  21 else if (name.compareToIgnoreCase("build-dir") == 0) {
 151  5 List<DocFile> fList = exp.getRest().accept(flv);
 152    // System.err.println("BuildDir fList = " + fList);
 153  0 if (fList.size() > 1) throw new PrivateProjectException("Cannot have multiple build directories");
 154  0 else if (fList.size() == 0) pfir.setBuildDirectory(FileOps.NULL_FILE);
 155  5 else pfir.setBuildDirectory(fList.get(0));
 156    }
 157  16 else if (name.compareToIgnoreCase("work-dir") == 0) {
 158  5 List<DocFile> fList = exp.getRest().accept(flv);
 159  0 if (fList.size() > 1) throw new PrivateProjectException("Cannot have multiple working directories");
 160    // else if (fList.size() == 0) pfir.setWorkingDirectory(null); // working directory should never be set to null;
 161  5 else pfir.setWorkingDirectory(fList.get(0));
 162    }
 163  11 else if (name.compareToIgnoreCase("classpaths") == 0) {
 164  5 List<DocFile> fList = exp.getRest().accept(flv);
 165  5 pfir.setClassPaths(fList);
 166    }
 167  6 else if (name.compareToIgnoreCase("main-class") == 0) {
 168  5 try{
 169  5 List<DocFile> fList = exp.getRest().accept(flv);
 170  0 if(fList.size() == 1){
 171  0 String main = fList.get(0).getAbsolutePath();
 172   
 173  0 pfir.setMainClass(main);
 174   
 175  0 return;
 176    }
 177    }catch(Exception exc){ }
 178   
 179  5 String mainClass = exp.getRest().accept(NameVisitor.ONLY);
 180  5 pfir.setMainClass(mainClass);
 181    }
 182  1 else if (name.compareToIgnoreCase("create-jar-file") == 0) {
 183  1 List<DocFile> fList = exp.getRest().accept(flv);
 184  0 if (fList.size() > 1) throw new PrivateProjectException("Cannot have more than one \"create jar\" file");
 185  0 else if (fList.size() == 0) pfir.setCreateJarFile(null);
 186  1 else pfir.setCreateJarFile(fList.get(0));
 187    }
 188  0 else if (name.compareToIgnoreCase("create-jar-flags") == 0) {
 189  0 Integer i = exp.getRest().accept(NumberVisitor.ONLY);
 190  0 pfir.setCreateJarFlags(i);
 191    }
 192  0 else if (name.compareToIgnoreCase("breakpoints") == 0) {
 193  0 List<DebugBreakpointData> bpList = exp.getRest().accept(breakpointListVisitor);
 194  0 pfir.setBreakpoints(bpList);
 195    }
 196  0 else if (name.compareToIgnoreCase("watches") == 0) {
 197  0 List<DebugWatchData> sList = exp.getRest().accept(WatchListVisitor.ONLY);
 198  0 pfir.setWatches(sList);
 199    }
 200  0 else if (name.compareToIgnoreCase("bookmarks") == 0) {
 201  0 List<FileRegion> bmList = exp.getRest().accept(bookmarkListVisitor);
 202  0 pfir.setBookmarks(bmList);
 203    }
 204    }
 205   
 206    /** Parses out the labeled node (a non-empty list) into a DocFile. The node must have the "file" label on it.
 207    * @param s the non-empty list expression
 208    * @return the DocFile described by this s-expression
 209    */
 210  76 DocFile parseFile(SExp s, String pathRoot) {
 211  76 String name = s.accept(NameVisitor.ONLY);
 212  76 if (name.compareToIgnoreCase("file") != 0)
 213  5 throw new PrivateProjectException("Expected a file tag, found: " + name);
 214  71 if (! (s instanceof Cons))
 215  0 throw new PrivateProjectException("Expected a labeled node, found a label: " + name);
 216  71 SEList c = ((Cons)s).getRest(); // get parameter list
 217   
 218  71 DocFilePropertyVisitor v = new DocFilePropertyVisitor(pathRoot);
 219  71 return c.accept(v);
 220    }
 221   
 222  71 private String parseFileName(SExp s) {
 223  71 if (s instanceof Cons) {
 224  71 SEList l = ((Cons)s).getRest();
 225  71 if (l == Empty.ONLY)
 226  0 throw new PrivateProjectException("expected filename, but nothing found");
 227    else {
 228  71 String name = l.accept(NameVisitor.ONLY);
 229  71 name = edu.rice.cs.util.StringOps.replace(name,"\\","/");
 230  71 return name;
 231    }
 232    }
 233  0 else throw new PrivateProjectException("expected name tag, found string");
 234    }
 235   
 236  0 private int parseInt(SExp s) {
 237  0 if (s instanceof Cons) {
 238  0 SEList l = ((Cons)s).getRest();
 239  0 if (l == Empty.ONLY)
 240  0 throw new PrivateProjectException("expected integer, but nothing found");
 241    else {
 242  0 int i = l.accept(NumberVisitor.ONLY);
 243  0 return i;
 244    }
 245    }
 246  0 else throw new PrivateProjectException("expected name tag, found string");
 247    }
 248   
 249  57 private Pair<Integer,Integer> parseIntPair(SExp s) {
 250    /* we're getting in a "(select # #)" */
 251  57 if (!(s instanceof Cons)) {
 252  0 throw new PrivateProjectException("expected name tag, found string");
 253    }
 254   
 255    // get rid of "select"
 256  57 final List<Integer> intList = new ArrayList<Integer>();
 257  57 SEList l = ((Cons)s).getRest();
 258  57 List<Integer> li = l.accept(new SExpVisitor<List<Integer>>() {
 259  57 public List<Integer> forEmpty(Empty e) { return intList; }
 260   
 261  114 public List<Integer> forCons(Cons c) {
 262  114 c.getFirst().accept(this);
 263  114 return c.getRest().accept(this);
 264    }
 265   
 266  0 public List<Integer> forBoolAtom(BoolAtom b) {
 267  0 throw new PrivateProjectException("unexpected boolean found, int expected");
 268    }
 269   
 270  114 public List<Integer> forNumberAtom(NumberAtom n) {
 271  114 intList.add(Integer.valueOf(n.intValue()));
 272  114 return intList;
 273    }
 274   
 275  0 public List<Integer> forTextAtom(TextAtom t) {
 276  0 throw new PrivateProjectException("unexpected string found where number expected: " + t.getText());
 277    }
 278   
 279    });
 280   
 281  57 if (li.size() == 2) return new Pair<Integer,Integer>(li.get(0), li.get(1));
 282  0 else throw new PrivateProjectException("expected a list of 2 ints for select, found list of size " + li.size());
 283    }
 284   
 285    /** Takes input of form "(str str)" and returns the second string. */
 286  48 private String parseStringNode(SExp n) {
 287  48 if (n instanceof Cons)
 288  48 return ((Cons)n).getRest().accept(NameVisitor.ONLY);
 289  0 else throw new PrivateProjectException("List expected, but found text instead");
 290    }
 291   
 292    /* nested/inner classes */
 293   
 294    /** Parses out a list of file nodes. */
 295    private static class DocFileListVisitor implements SEListVisitor<List<DocFile>> {
 296    /** Base directory for relative paths */
 297    private String _base;
 298  52 DocFileListVisitor(String base) { _base = base; }
 299  39 public List<DocFile> forEmpty(Empty e) { return new ArrayList<DocFile>(); }
 300  75 public List<DocFile> forCons(Cons c) {
 301  75 List<DocFile> list = c.getRest().accept(this);
 302  75 DocFile tmp = ProjectFileParser.ONLY.parseFile(c.getFirst(), _base);
 303  70 list.add(0, tmp); // add to the end
 304  70 return list;
 305    }
 306    };
 307   
 308    /** Traverses the list of expressions found after "file" tag and returns the DocFile described by those properties. */
 309    private static class DocFilePropertyVisitor implements SEListVisitor<DocFile> {
 310    private String fname = "";
 311    private Pair<Integer,Integer> select = new Pair<Integer,Integer>(Integer.valueOf(0), Integer.valueOf(0));
 312    private Pair<Integer,Integer> scroll = new Pair<Integer,Integer>(Integer.valueOf(0), Integer.valueOf(0));
 313    private boolean active = false;
 314    private String pack = "";
 315    private Date modDate = null;
 316   
 317    private String pathRoot;
 318  71 public DocFilePropertyVisitor(String pr) { pathRoot = pr; }
 319   
 320  167 public DocFile forCons(Cons c) {
 321  167 String name = c.getFirst().accept(NameVisitor.ONLY);
 322  71 if (name.compareToIgnoreCase("name") == 0) { fname = ProjectFileParser.ONLY.parseFileName(c.getFirst()); }
 323  50 else if (name.compareToIgnoreCase("select") == 0) { select = ProjectFileParser.ONLY.parseIntPair(c.getFirst()); }
 324  7 else if (name.compareToIgnoreCase("scroll") == 0) { scroll = ProjectFileParser.ONLY.parseIntPair(c.getFirst()); }
 325  0 else if (name.compareToIgnoreCase("active") == 0) { active = true; }
 326  39 else if (name.compareToIgnoreCase("package") == 0) {
 327  7 pack = ProjectFileParser.ONLY.parseStringNode(c.getFirst());
 328    }
 329  32 else if (name.compareToIgnoreCase("mod-date") == 0) {
 330  32 String tmp = ProjectFileParser.ONLY.parseStringNode(c.getFirst());
 331  32 try {
 332    //attemp parsing in default locale
 333  32 modDate = ProjectProfile.MOD_DATE_FORMAT.parse(tmp); }
 334    catch (java.text.ParseException e1) {
 335    //parsing in default locale failed
 336  0 try {
 337    //attempt parsing in current locale
 338  0 modDate = new SimpleDateFormat(ProjectProfile.MOD_DATE_FORMAT_STRING).parse(tmp);
 339    } catch (java.text.ParseException e2) {
 340    //both parsings failed
 341  0 throw new PrivateProjectException("Bad mod-date: " + e2.getMessage());
 342    }
 343    }
 344    }
 345   
 346  167 return c.getRest().accept(this);
 347    }
 348   
 349  71 public DocFile forEmpty(Empty c) {
 350  71 if (pathRoot == null || new File(fname).isAbsolute()) {
 351  24 return new DocFile(fname, select, scroll, active, pack);
 352    }
 353    else {
 354  47 DocFile f = new DocFile(pathRoot, fname, select, scroll, active, pack);
 355  24 if (modDate != null) f.setSavedModDate(modDate.getTime());
 356  47 return f;
 357    }
 358    }
 359    }
 360   
 361    /** Parses out a list of path nodes into a list of Strings. */
 362    private static class PathListVisitor implements SEListVisitor<List<String>> {
 363    public static final PathListVisitor ONLY = new PathListVisitor();
 364  1 private PathListVisitor() { }
 365   
 366  5 public List<String> forEmpty(Empty e) { return new ArrayList<String>(); }
 367  9 public List<String> forCons(Cons c) {
 368  9 List<String> list = c.getRest().accept(this);
 369  9 SExp first = c.getFirst();
 370  9 String name = first.accept(NameVisitor.ONLY);
 371  9 if (name.compareToIgnoreCase("path") == 0) {
 372  9 String tmp = ProjectFileParser.ONLY.parseStringNode(c.getFirst());
 373  9 list.add(0,tmp); // add to the end
 374    }
 375  9 return list;
 376    }
 377    };
 378   
 379    /** Retrieves the name of a node. The node should either be a list with its first element being a text atom,
 380    * or a text atom itself.
 381    */
 382    private static class NameVisitor implements SExpVisitor<String> {
 383    public static final NameVisitor ONLY = new NameVisitor();
 384  2 private NameVisitor() { }
 385   
 386  0 public String forEmpty(Empty e) {
 387  0 throw new PrivateProjectException("Found an empty node, expected a labeled node");
 388    }
 389  415 public String forCons(Cons c) { return c.getFirst().accept(this); }
 390  0 public String forBoolAtom(BoolAtom b) {
 391  0 throw new PrivateProjectException("Found a boolean, expected a label");
 392    }
 393  0 public String forNumberAtom(NumberAtom n) {
 394  0 throw new PrivateProjectException("Found a number, expected a label");
 395    }
 396  420 public String forTextAtom(TextAtom t) { return t.getText(); }
 397    };
 398   
 399    /** Retrieves the number of a node. The node should either be a list with its first element being a number atom,
 400    * or a number atom itself.
 401    */
 402    private static class NumberVisitor implements SExpVisitor<Integer> {
 403    public static final NumberVisitor ONLY = new NumberVisitor();
 404  0 private NumberVisitor() { }
 405   
 406  0 public Integer forEmpty(Empty e) {
 407  0 throw new PrivateProjectException("Found an empty node, expected an integer");
 408    }
 409  0 public Integer forCons(Cons c) { return c.getFirst().accept(this); }
 410  0 public Integer forBoolAtom(BoolAtom b) {
 411  0 throw new PrivateProjectException("Found a boolean, expected an integer");
 412    }
 413  0 public Integer forNumberAtom(NumberAtom n) { return n.intValue(); }
 414  0 public Integer forTextAtom(TextAtom t) {
 415  0 throw new PrivateProjectException("Found a string '" + t + "', expected an integer");
 416    }
 417    };
 418   
 419    /** Parses out a list of watch names into a list of watches. */
 420    private static class WatchListVisitor implements SEListVisitor<List<DebugWatchData>> {
 421    public static final WatchListVisitor ONLY = new WatchListVisitor();
 422  0 private WatchListVisitor() { }
 423   
 424  0 public List<DebugWatchData> forEmpty(Empty e) { return new ArrayList<DebugWatchData>(); }
 425  0 public List<DebugWatchData> forCons(Cons c) {
 426  0 List<DebugWatchData> list = c.getRest().accept(this);
 427  0 SExp first = c.getFirst();
 428  0 String name = first.accept(NameVisitor.ONLY);
 429  0 if (name.compareToIgnoreCase("watch") == 0) {
 430  0 String tmp = ProjectFileParser.ONLY.parseStringNode(c.getFirst());
 431  0 list.add(0,new DebugWatchData(tmp)); // add to the end
 432    }
 433  0 return list;
 434    }
 435    };
 436   
 437    // === breakpoints ===
 438   
 439    /** Parses out a list of breakpoint nodes. */
 440    private class BreakpointListVisitor implements SEListVisitor<List<DebugBreakpointData>> {
 441  0 public List<DebugBreakpointData> forEmpty(Empty e) { return new ArrayList<DebugBreakpointData>(); }
 442  0 public List<DebugBreakpointData> forCons(Cons c) {
 443  0 List<DebugBreakpointData> list = c.getRest().accept(this);
 444  0 DebugBreakpointData tmp = ProjectFileParser.ONLY.parseBreakpoint(c.getFirst(), _srcFileBase);
 445  0 list.add(0, tmp); // add to the end
 446  0 return list;
 447    }
 448    };
 449   
 450    /** Parses out the labeled node (a non-empty list) into a breakpoint. The node must have the "breakpoint" label on it.
 451    * @param s the non-empty list expression
 452    * @return the breakpoint described by this s-expression
 453    */
 454  0 DebugBreakpointData parseBreakpoint(SExp s, String pathRoot) {
 455  0 String name = s.accept(NameVisitor.ONLY);
 456  0 if (name.compareToIgnoreCase("breakpoint") != 0)
 457  0 throw new PrivateProjectException("Expected a breakpoint tag, found: " + name);
 458  0 if (! (s instanceof Cons))
 459  0 throw new PrivateProjectException("Expected a labeled node, found a label: " + name);
 460  0 SEList c = ((Cons)s).getRest(); // get parameter list
 461   
 462  0 BreakpointPropertyVisitor v = new BreakpointPropertyVisitor(pathRoot);
 463  0 return c.accept(v);
 464    }
 465   
 466   
 467    /** Traverses the list of expressions found after "breakpoint" tag and returns the Breakpoint described by those properties. */
 468    private static class BreakpointPropertyVisitor implements SEListVisitor<DebugBreakpointData> {
 469    private String fname = null;
 470    // private Integer offset = null; // Not used.
 471    private Integer lineNumber = null;
 472    private boolean isEnabled = false;
 473   
 474    private String pathRoot;
 475  0 public BreakpointPropertyVisitor(String pr) { pathRoot = pr; }
 476   
 477  0 public DebugBreakpointData forCons(Cons c) {
 478  0 String name = c.getFirst().accept(NameVisitor.ONLY);
 479  0 if (name.compareToIgnoreCase("name") == 0) { fname = ProjectFileParser.ONLY.parseFileName(c.getFirst()); }
 480    // The offset field is not used.
 481    // else if (name.compareToIgnoreCase("offset") == 0) { offset = ProjectFileParser.ONLY.parseInt(c.getFirst()); }
 482  0 else if (name.compareToIgnoreCase("line") == 0) { lineNumber = ProjectFileParser.ONLY.parseInt(c.getFirst()); }
 483  0 else if (name.compareToIgnoreCase("enabled") == 0) { isEnabled = true; }
 484   
 485  0 return c.getRest().accept(this);
 486    }
 487   
 488  0 public DebugBreakpointData forEmpty(Empty c) {
 489  0 if ((fname == null) || (lineNumber == null)) {
 490  0 throw new PrivateProjectException("Breakpoint information incomplete, need name and line tags");
 491    }
 492  0 if (pathRoot == null || new File(fname).isAbsolute()) {
 493  0 final File f = new File(fname);
 494  0 return new DebugBreakpointData() {
 495  0 public File getFile() { return f; }
 496  0 public int getLineNumber() { return lineNumber; }
 497  0 public boolean isEnabled() { return isEnabled; }
 498    };
 499    }
 500    else {
 501  0 final File f = new File(pathRoot, fname);
 502  0 return new DebugBreakpointData() {
 503  0 public File getFile() { return f; }
 504  0 public int getLineNumber() { return lineNumber; }
 505  0 public boolean isEnabled() { return isEnabled; }
 506    };
 507    }
 508    }
 509    }
 510   
 511    // === bookmarks ===
 512   
 513    /** Parses out a list of bookmark nodes. */
 514    private class BookmarkListVisitor implements SEListVisitor<List<FileRegion>> {
 515  0 public List<FileRegion> forEmpty(Empty e) { return new ArrayList<FileRegion>(); }
 516  0 public List<FileRegion> forCons(Cons c) {
 517  0 List<FileRegion> list = c.getRest().accept(this);
 518  0 FileRegion tmp = ProjectFileParser.ONLY.parseBookmark(c.getFirst(), _srcFileBase);
 519  0 list.add(0, tmp); // add to the end
 520  0 return list;
 521    }
 522    };
 523   
 524    /** Parses out the labeled node (a non-empty list) into a bookmark. The node must have the "bookmark" label on it.
 525    * @param s the non-empty list expression
 526    * @return the bookmark described by this s-expression
 527    */
 528  0 FileRegion parseBookmark(SExp s, String pathRoot) {
 529  0 String name = s.accept(NameVisitor.ONLY);
 530  0 if (name.compareToIgnoreCase("bookmark") != 0)
 531  0 throw new PrivateProjectException("Expected a bookmark tag, found: " + name);
 532  0 if (! (s instanceof Cons))
 533  0 throw new PrivateProjectException("Expected a labeled node, found a label: " + name);
 534  0 SEList c = ((Cons)s).getRest(); // get parameter list
 535   
 536  0 BookmarkPropertyVisitor v = new BookmarkPropertyVisitor(pathRoot);
 537  0 return c.accept(v);
 538    }
 539   
 540   
 541    /** Traverses the list of expressions found after "bookmark" tag and returns the DocumentRegion
 542    * described by those properties. */
 543    private static class BookmarkPropertyVisitor implements SEListVisitor<FileRegion> {
 544    private String fname = null;
 545    private Integer startOffset = null;
 546    private Integer endOffset = null;
 547   
 548    private String pathRoot;
 549  0 public BookmarkPropertyVisitor(String pr) { pathRoot = pr; }
 550   
 551  0 public FileRegion forCons(Cons c) {
 552  0 String name = c.getFirst().accept(NameVisitor.ONLY);
 553  0 if (name.compareToIgnoreCase("name") == 0) { fname = ProjectFileParser.ONLY.parseFileName(c.getFirst()); }
 554  0 else if (name.compareToIgnoreCase("start") == 0) { startOffset = ProjectFileParser.ONLY.parseInt(c.getFirst()); }
 555  0 else if (name.compareToIgnoreCase("end") == 0) { endOffset = ProjectFileParser.ONLY.parseInt(c.getFirst()); }
 556   
 557  0 return c.getRest().accept(this);
 558    }
 559   
 560  0 public FileRegion forEmpty(Empty c) {
 561  0 if ((fname == null) || (startOffset == null) || (endOffset == null)) {
 562  0 throw new PrivateProjectException("Bookmark information incomplete, need name, start offset and end offset");
 563    }
 564  0 File f;
 565  0 if (pathRoot == null || new File(fname).isAbsolute()) f = new File(fname);
 566  0 else f = new File(pathRoot, fname);
 567  0 return new DummyDocumentRegion(f, startOffset, endOffset);
 568    }
 569    }
 570   
 571    private static class PrivateProjectException extends RuntimeException {
 572  5 public PrivateProjectException(String message) { super(message); }
 573    }
 574    }