1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.doclava;
18
19import java.util.Arrays;
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.Comparator;
23import java.util.List;
24import java.util.regex.Pattern;
25import java.util.regex.Matcher;
26import java.io.File;
27
28import com.google.clearsilver.jsilver.data.Data;
29
30/**
31* Represents a browsable sample code project, with methods for managing
32* metadata collection, file output, sorting, etc.
33*/
34public class SampleCode {
35  String mSource;
36  String mDest;
37  String mTitle;
38  String mProjectDir;
39  String mTags;
40
41  /** Max size for browseable images/video. If a source file exceeds this size,
42  * a file is generated with a generic placeholder and the original file is not
43  * copied to out.
44  */
45  private static final double MAX_FILE_SIZE_BYTES = 2097152;
46
47  /** When full tree nav is enabled, generate an index for every dir
48  * and linkify the breadcrumb paths in all files.
49  */
50  private static final boolean FULL_TREE_NAVIGATION = false;
51
52  public SampleCode(String source, String dest, String title) {
53    mSource = source;
54    mTitle = title;
55    mTags = null;
56
57    if (dest != null) {
58      int len = dest.length();
59      if (len > 1 && dest.charAt(len - 1) != '/') {
60        mDest = dest + '/';
61      } else {
62        mDest = dest;
63      }
64    }
65  }
66
67  /**
68  * Iterates a given sample code project gathering  metadata for files and building
69  * a node tree that reflects the project's directory structure. After iterating
70  * the project, this method adds the project's metadata to jd_lists_unified,
71  * so that it is accessible for dynamic content and search suggestions.
72  *
73  * @param offlineMode Ignored -- offline-docs mode is not currently supported for
74  *        browsable sample code projects.
75  * @return A root Node for the project containing its metadata and tree structure.
76  */
77  public Node setSamplesTOC(boolean offlineMode) {
78    List<Node> filelist = new ArrayList<Node>();
79    File f = new File(mSource);
80    mProjectDir = f.getName();
81    String name = mProjectDir;
82    String mOut = mDest + name;
83    if (!f.isDirectory()) {
84      System.out.println("-samplecode not a directory: " + mSource);
85      return null;
86    }
87
88    Data hdf = Doclava.makeHDF();
89    setProjectStructure(filelist, f, mDest);
90    String link = ClearPage.toroot + "samples/" + name + "/index" + Doclava.htmlExtension;
91    Node rootNode = writeSampleIndexCs(hdf, f,
92        new Node.Builder().setLabel(mProjectDir).setLink(link).setChildren(filelist).build(),false);
93    return rootNode;
94  }
95
96  /**
97  * For a given sample code project dir, iterate through the project generating
98  * browsable html for all valid sample code files. After iterating the project
99  * generate a templated index file to the project output root.
100  *
101  * @param offlineMode Ignored -- offline-docs mode is not currently supported for
102  *        browsable sample code projects.
103  */
104  public void writeSamplesFiles(boolean offlineMode) {
105    List<Node> filelist = new ArrayList<Node>();
106    File f = new File(mSource);
107    mProjectDir = f.getName();
108    String name = mProjectDir;
109    String mOut = mDest + name;
110    if (!f.isDirectory()) {
111      System.out.println("-samplecode not a directory: " + mSource);
112    }
113
114    Data hdf = Doclava.makeHDF();
115    if (Doclava.samplesNavTree != null) {
116      hdf.setValue("samples_toc_tree", Doclava.samplesNavTree.getValue("samples_toc_tree", ""));
117    }
118    hdf.setValue("samples", "true");
119    hdf.setValue("projectDir", mProjectDir);
120    writeProjectDirectory(f, mDest, false, hdf, "Files.");
121    writeProjectStructure(name, hdf);
122    hdf.removeTree("parentdirs");
123    hdf.setValue("parentdirs.0.Name", name);
124    boolean writeFiles = true;
125    String link = "samples/" + name + "/index" + Doclava.htmlExtension;
126    //Write root _index.jd to out and add metadata to Node.
127    writeSampleIndexCs(hdf, f,
128        new Node.Builder().setLabel(mProjectDir).setLink(link).build(), true);
129  }
130
131  /**
132  * Given the root Node for a sample code project, iterates through the project
133  * gathering metadata and project tree structure. Unsupported file types are
134  * filtered from the project output. The collected project Nodes are appended to
135  * the root project node.
136  *
137  * @param parent The root Node that represents this sample code project.
138  * @param dir The current dir being processed.
139  * @param relative Relative path for creating links to this file.
140  */
141  public void setProjectStructure(List<Node> parent, File dir, String relative) {
142    String name, link;
143    File[] dirContents = dir.listFiles();
144    Arrays.sort(dirContents, byTypeAndName);
145    for (File f: dirContents) {
146      name = f.getName();
147      if (!isValidFiletype(name)) {
148        continue;
149      }
150      if (f.isFile() && name.contains(".")) {
151        String path = relative + name;
152        link = convertExtension(path, Doclava.htmlExtension);
153        if (inList(path, IMAGES) || inList(path, VIDEOS) || inList(path, TEMPLATED)) {
154          parent.add(new Node.Builder().setLabel(name).setLink(ClearPage.toroot + link).build());
155        }
156      } else if (f.isDirectory()) {
157        List<Node> mchildren = new ArrayList<Node>();
158        String dirpath = relative + name + "/";
159        setProjectStructure(mchildren, f, dirpath);
160        if (mchildren.size() > 0) {
161          parent.add(new Node.Builder().setLabel(name).setLink(ClearPage.toroot
162            + dirpath).setChildren(mchildren).build());
163        }
164      }
165    }
166  }
167
168  /**
169  * Given a root sample code project path, iterates through the project
170  * setting page metadata to manage html output and writing/copying files to
171  * the output directory. Source files are templated and images are templated
172  * and linked to the original image.
173  *
174  * @param dir The current dir being processed.
175  * @param relative Relative path for creating links to this file.
176  * @param recursed Whether the method is being called recursively.
177  * @param hdf The data to read/write for files in this project.
178  * @param newKey Key passed in recursion for managing cs child trees.
179  */
180  public void writeProjectDirectory(File dir, String relative, Boolean recursed,
181      Data hdf, String newkey) {
182    String name = "";
183    String link = "";
184    String type = "";
185    int i = 0;
186    String expansion = ".Sub.";
187    String key = newkey;
188
189    if (recursed) {
190      key = (key + expansion);
191    } else {
192      expansion = "";
193    }
194
195    File[] dirContents = dir.listFiles();
196    Arrays.sort(dirContents, byTypeAndName);
197    for (File f: dirContents) {
198      name = f.getName();
199      if (!isValidFiletype(name)) {
200        continue;
201      }
202      if (f.isFile() && name.contains(".")) {
203        String path = relative + name;
204        type = mapTypes(name);
205        link = convertExtension(path, Doclava.htmlExtension);
206        if (inList(path, IMAGES)) {
207          type = "img";
208          if (f.length() < MAX_FILE_SIZE_BYTES) {
209            ClearPage.copyFile(false, f, path);
210            writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension),
211                relative, type, true);
212          } else {
213            writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension),
214                relative, type, false);
215          }
216          hdf.setValue(key + i + ".Type", "img");
217          hdf.setValue(key + i + ".Name", name);
218          hdf.setValue(key + i + ".Href", link);
219          hdf.setValue(key + i + ".RelPath", relative);
220        } else if (inList(path, VIDEOS)) {
221          type = "video";
222          if (f.length() < MAX_FILE_SIZE_BYTES) {
223            ClearPage.copyFile(false, f, path);
224            writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension),
225                relative, type, true);
226          } else {
227            writeImageVideoPage(f, convertExtension(path, Doclava.htmlExtension),
228                relative, type, false);
229          }
230          hdf.setValue(key + i + ".Type", "video");
231          hdf.setValue(key + i + ".Name", name);
232          hdf.setValue(key + i + ".Href", link);
233          hdf.setValue(key + i + ".RelPath", relative);
234        } else if (inList(path, TEMPLATED)) {
235          writePage(f, convertExtension(path, Doclava.htmlExtension), relative, hdf);
236          hdf.setValue(key + i + ".Type", type);
237          hdf.setValue(key + i + ".Name", name);
238          hdf.setValue(key + i + ".Href", link);
239          hdf.setValue(key + i + ".RelPath", relative);
240        }
241        i++;
242      } else if (f.isDirectory()) {
243        List<Node> mchildren = new ArrayList<Node>();
244        type = "dir";
245        String dirpath = relative + name;
246        link = dirpath + "/index" + Doclava.htmlExtension;
247         String hdfkeyName = (key + i + ".Name");
248         String hdfkeyType = (key + i + ".Type");
249         String hdfkeyHref = (key + i + ".Href");
250        hdf.setValue(hdfkeyName, name);
251        hdf.setValue(hdfkeyType, type);
252        hdf.setValue(hdfkeyHref, relative + name + "/" + "index" + Doclava.htmlExtension);
253        writeProjectDirectory(f, relative + name + "/", true, hdf, (key + i));
254        i++;
255      }
256    }
257
258    setParentDirs(hdf, relative, name, false);
259    //Generate an index.html page for each dir being processed
260    if (FULL_TREE_NAVIGATION) {
261      ClearPage.write(hdf, "sampleindex.cs", relative + "/index" + Doclava.htmlExtension);
262    }
263  }
264
265  /**
266  * Processes a templated project index page from _index.jd in a project root.
267  * Each sample project must have an index, and each index locally defines it's own
268  * page.tags and sample.group cs vars. This method takes a SC node on input, reads
269  * any local vars from the _index.jd, optionally generates an html file to out,
270  * then updates the SC node with the page vars and returns it to the caller.
271  *
272  * @param hdf The data source to read/write for this index file.
273  * @param dir The sample project root directory.
274  * @param tnode A Node to serve as the project's root node.
275  * @param writeFiles If true, generates output files only. If false, collects
276  *        metadata only.
277  * @return The tnode root with any metadata/child Nodes appended.
278  */
279  public Node writeSampleIndexCs(Data hdf, File dir, Node tnode, boolean writeFiles) {
280
281    String filename = dir.getAbsolutePath() + "/_index.jd";
282    String mGroup = "";
283    File f = new File(filename);
284    String rel = dir.getPath();
285    if (writeFiles) {
286
287      hdf.setValue("samples", "true");
288      //set any default page variables for root index
289      hdf.setValue("page.title", mProjectDir);
290      hdf.setValue("projectDir", mProjectDir);
291      hdf.setValue("projectTitle", mTitle);
292      //add the download/project links to the landing pages.
293      hdf.setValue("samplesProjectIndex", "true");
294      if (!f.isFile()) {
295        //The directory didn't have an _index.jd, so create a stub.
296        ClearPage.write(hdf, "sampleindex.cs", mDest + "index" + Doclava.htmlExtension);
297      } else {
298        DocFile.writePage(filename, rel, mDest + "index" + Doclava.htmlExtension, hdf);
299      }
300    } else if (f.isFile()) {
301      //gather metadata for toc and jd_lists_unified
302      DocFile.getPageMetadata(filename, hdf);
303      mGroup = hdf.getValue("sample.group", "");
304      if (!"".equals(mGroup)) {
305        tnode.setGroup(hdf.getValue("sample.group", ""));
306      } else {
307        //Errors.error(Errors.INVALID_SAMPLE_INDEX, null, "Sample " + mProjectDir
308        //          + ": Root _index.jd must be present and must define sample.group"
309        //          + " tag. Please see ... for details.");
310      }
311    }
312    return tnode;
313  }
314
315  /**
316  * Sets metadata for managing html output and generates the project view page
317  * for a project.
318  *
319  * @param dir The project root dir.
320  * @param hdf The data to read/write for files in this project.
321  */
322  public void writeProjectStructure(String dir, Data hdf) {
323    hdf.setValue("projectStructure", "true");
324    hdf.setValue("projectDir", mProjectDir);
325    hdf.setValue("page.title", mProjectDir + " Structure");
326    hdf.setValue("projectTitle", mTitle);
327    ClearPage.write(hdf, "sampleindex.cs", mDest + "project" + Doclava.htmlExtension);
328    hdf.setValue("projectStructure", "");
329  }
330
331  /**
332  * Keeps track of each file's parent dirs. Used for generating path breadcrumbs in html.
333  *
334  * @param dir The data to read/write for this file.
335  * @param hdf The relative path for this file, from samples root.
336  * @param subdir The relative path for this file, from samples root.
337  * @param name The name of the file (minus extension).
338  * @param isFile Whether this is a file (not a dir).
339  */
340  Data setParentDirs(Data hdf, String subdir, String name, Boolean isFile) {
341    if (FULL_TREE_NAVIGATION) {
342      hdf.setValue("linkfyPathCrumb", "");
343    }
344    int iter;
345    hdf.removeTree("parentdirs");
346    String s = subdir;
347    String urlParts[] = s.split("/");
348    int n, l = 1;
349    for (iter=1; iter < urlParts.length; iter++) {
350      n = iter-1;
351      hdf.setValue("parentdirs." + n + ".Name", urlParts[iter]);
352      hdf.setValue("parentdirs." + n + ".Link", subdir + "index" + Doclava.htmlExtension);
353    }
354    return hdf;
355  }
356
357  /**
358  * Writes a templated source code file to out.
359  */
360  public void writePage(File f, String out, String subdir, Data hdf) {
361    String name = f.getName();
362    String path = f.getPath();
363    String data = SampleTagInfo.readFile(new SourcePositionInfo(path, -1, -1), path,
364        "sample code", true, true, true, true);
365    data = Doclava.escape(data);
366
367    String relative = subdir.replaceFirst("samples/", "");
368    setParentDirs(hdf, subdir, name, true);
369    hdf.setValue("projectTitle", mTitle);
370    hdf.setValue("projectDir", mProjectDir);
371    hdf.setValue("page.title", name);
372    hdf.setValue("subdir", subdir);
373    hdf.setValue("relative", relative);
374    hdf.setValue("realFile", name);
375    hdf.setValue("fileContents", data);
376    hdf.setValue("resTag", "sample");
377
378    ClearPage.write(hdf, "sample.cs", out);
379  }
380
381  /**
382  * Writes a templated image or video file to out.
383  */
384  public void writeImageVideoPage(File f, String out, String subdir,
385        String resourceType, boolean browsable) {
386    Data hdf = Doclava.makeHDF();
387    if (Doclava.samplesNavTree != null) {
388      hdf.setValue("samples_toc_tree", Doclava.samplesNavTree.getValue("samples_toc_tree", ""));
389    }
390    hdf.setValue("samples", "true");
391
392    String name = f.getName();
393    if (!browsable) {
394      hdf.setValue("noDisplay", "true");
395    }
396    setParentDirs(hdf, subdir, name, true);
397    hdf.setValue("samples", "true");
398    hdf.setValue("page.title", name);
399    hdf.setValue("projectTitle", mTitle);
400    hdf.setValue("projectDir", mProjectDir);
401    hdf.setValue("subdir", subdir);
402    hdf.setValue("resType", resourceType);
403    hdf.setValue("realFile", name);
404
405    ClearPage.write(hdf, "sample.cs", out);
406  }
407
408  /**
409  * Given a node containing sample code projects and a node containing all valid
410  * group nodes, extract project nodes from tnode and append them to the group node
411  * that matches their sample.group metadata.
412  *
413  * @param tnode A list of nodes containing sample code projects.
414  * @param groupnodes A list of nodes that represent the valid sample groups.
415  * @return The groupnodes list with all projects appended properly to their
416  *         associated sample groups.
417  */
418  public static void writeSamplesNavTree(List<Node> tnode, List<Node> groupnodes) {
419
420    Node node = new Node.Builder().setLabel("Samples").setLink(ClearPage.toroot
421        + "samples/index" + Doclava.htmlExtension).setChildren(tnode).build();
422
423    if (groupnodes != null) {
424      for (int i = 0; i < tnode.size(); i++) {
425        if (tnode.get(i) != null) {
426          groupnodes = appendNodeGroups(tnode.get(i), groupnodes);
427        }
428      }
429      for (int n = 0; n < groupnodes.size(); n++) {
430        if (groupnodes.get(n).getChildren() == null) {
431          groupnodes.remove(n);
432          n--;
433        } else {
434          Collections.sort(groupnodes.get(n).getChildren(), byLabel);
435        }
436      }
437      node.setChildren(groupnodes);
438    }
439
440    StringBuilder buf = new StringBuilder();
441    node.renderGroupNodesTOC(buf);
442    if (Doclava.samplesNavTree != null) {
443          Doclava.samplesNavTree.setValue("samples_toc_tree", buf.toString());
444    }
445
446  }
447
448  /**
449  * For a given project root node, get the group and then iterate the list of valid
450  * groups looking for a match. If found, append the project to that group node.
451  * Samples that reference a valid sample group tag are added to a list for that
452  * group. Samples declare a sample.group tag in their _index.jd files.
453  */
454  private static List<Node> appendNodeGroups(Node gNode, List<Node> groupnodes) {
455    List<Node> mgrouplist = new ArrayList<Node>();
456    for (int i = 0; i < groupnodes.size(); i++) {
457      if (gNode.getGroup().equals(groupnodes.get(i).getLabel())) {
458        if (groupnodes.get(i).getChildren() == null) {
459          mgrouplist.add(gNode);
460          groupnodes.get(i).setChildren(mgrouplist);
461        } else {
462          groupnodes.get(i).getChildren().add(gNode);
463        }
464        break;
465      }
466    }
467    return groupnodes;
468  }
469
470  /**
471  * Sorts an array of files by type and name (alpha), with manifest always at top.
472  */
473  Comparator<File> byTypeAndName = new Comparator<File>() {
474    public int compare (File one, File other) {
475      if (one.isDirectory() && !other.isDirectory()) {
476        return 1;
477      } else if (!one.isDirectory() && other.isDirectory()) {
478        return -1;
479      } else if ("AndroidManifest.xml".equals(one.getName())) {
480        return -1;
481      } else {
482        return one.compareTo(other);
483      }
484    }
485  };
486
487  /**
488  * Sorts a list of Nodes by label.
489  */
490  public static Comparator<Node> byLabel = new Comparator<Node>() {
491    public int compare(Node one, Node other) {
492      return one.getLabel().compareTo(other.getLabel());
493    }
494  };
495
496  /**
497  * Concatenates dirs that only hold dirs, to simplify nav tree
498  */
499  public static List<Node> squashNodes(List<Node> tnode) {
500    List<Node> list = tnode;
501
502    for(int i = 0; i < list.size(); ++i) {
503      if (("dir".equals(list.get(i).getType())) &&
504          (list.size() == 1) &&
505          (list.get(i).getChildren().get(0).getChildren() != null)) {
506        String thisLabel = list.get(i).getLabel();
507        String childLabel =  list.get(i).getChildren().get(0).getLabel();
508        String newLabel = thisLabel + "/" + childLabel;
509        list.get(i).setLabel(newLabel);
510        list.get(i).setChildren(list.get(i).getChildren().get(0).getChildren());
511      } else {
512        continue;
513      }
514    }
515    return list;
516  }
517
518  public static String convertExtension(String s, String ext) {
519    return s.substring(0, s.lastIndexOf('.')) + ext;
520  }
521
522  /**
523  * Whitelists of valid image/video and source code types.
524  */
525  public static String[] IMAGES = {".png", ".jpg", ".gif"};
526  public static String[] VIDEOS = {".mp4", ".ogv", ".webm"};
527  public static String[] TEMPLATED = {".java", ".xml", ".aidl", ".rs",".txt", ".TXT"};
528
529  public static boolean inList(String s, String[] list) {
530    for (String t : list) {
531      if (s.endsWith(t)) {
532        return true;
533      }
534    }
535    return false;
536  }
537
538  /**
539  * Maps filenames to a set of generic types. Used for displaying files/dirs
540  * in the project view page.
541  */
542  public static String mapTypes(String name) {
543    String type = name.substring(name.lastIndexOf('.') + 1, name.length());
544    if ("xml".equals(type) || "java".equals(type)) {
545      if ("AndroidManifest.xml".equals(name)) type = "manifest";
546      return type;
547    } else {
548      return type = "file";
549    }
550  }
551
552  /**
553  * Validates a source file from a project against restrictions to determine
554  * whether to include the file in the browsable project output.
555  */
556  public boolean isValidFiletype(String name) {
557    if (name.startsWith(".") ||
558        name.startsWith("_") ||
559        "default.properties".equals(name) ||
560        "build.properties".equals(name) ||
561        name.endsWith(".ttf") ||
562        name.endsWith(".gradle") ||
563        name.endsWith(".bat") ||
564        "Android.mk".equals(name)) {
565      return false;
566    } else {
567      return true;
568    }
569  }
570
571  /**
572  * SampleCode variant of NavTree node.
573  */
574  public static class Node {
575    private String mLabel;
576    private String mLink;
577    private String mGroup; // from sample.group in _index.jd
578    private List<Node> mChildren;
579    private String mType;
580
581    private Node(Builder builder) {
582      mLabel = builder.mLabel;
583      mLink = builder.mLink;
584      mGroup = builder.mGroup;
585      mChildren = builder.mChildren;
586      mType = builder.mType;
587    }
588
589    public static class Builder {
590      private String mLabel, mLink, mGroup, mType;
591      private List<Node> mChildren = null;
592      public Builder setLabel(String mLabel) { this.mLabel = mLabel; return this;}
593      public Builder setLink(String mLink) { this.mLink = mLink; return this;}
594      public Builder setGroup(String mGroup) { this.mGroup = mGroup; return this;}
595      public Builder setChildren(List<Node> mChildren) { this.mChildren = mChildren; return this;}
596      public Builder setType(String mType) { this.mType = mType; return this;}
597      public Node build() {return new Node(this);}
598    }
599
600    /**
601    * Renders browsable sample groups and projects to an html list, starting
602    * from the group nodes and then rendering their project nodes and finally their
603    * child dirs and files.
604    */
605    void renderGroupNodesTOC(StringBuilder buf) {
606      List<Node> list = mChildren;
607      if (list == null || list.size() == 0) {
608        return;
609      } else {
610        final int n = list.size();
611        for (int i = 0; i < n; i++) {
612          if (list.get(i).getChildren() == null) {
613            continue;
614          } else {
615            buf.append("<li class=\"nav-section\">");
616            buf.append("<div class=\"nav-section-header\">");
617            buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
618                + list.get(i).getLabel() + "\">"
619                + list.get(i).getLabel() + "</a>");
620            buf.append("</div>");
621            buf.append("<ul>");
622            list.get(i).renderProjectNodesTOC(buf);
623          }
624        }
625        buf.append("</ul>");
626        buf.append("</li>");
627      }
628    }
629
630    /**
631    * Renders a list of sample code projects associated with a group node.
632    */
633    void renderProjectNodesTOC(StringBuilder buf) {
634      List<Node> list = mChildren;
635      if (list == null || list.size() == 0) {
636        return;
637      } else {
638        final int n = list.size();
639        for (int i = 0; i < n; i++) {
640          if (list.get(i).getChildren() == null) {
641            continue;
642          } else {
643            buf.append("<li class=\"nav-section\">");
644            buf.append("<div class=\"nav-section-header\">");
645            buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
646                + list.get(i).getLabel() + "\">"
647                + list.get(i).getLabel() + "</a>");
648            buf.append("</div>");
649            buf.append("<ul>");
650            list.get(i).renderChildrenToc(buf);
651          }
652        }
653        buf.append("</ul>");
654        buf.append("</li>");
655      }
656    }
657
658    /**
659    * Renders child dirs and files associated with a project node.
660    */
661    void renderChildrenToc(StringBuilder buf) {
662      List<Node> list = mChildren;
663      if (list == null || list.size() == 0) {
664        buf.append("null");
665      } else {
666        final int n = list.size();
667        for (int i = 0; i < n; i++) {
668          if (list.get(i).getChildren() == null) {
669            buf.append("<li>");
670            buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
671                + list.get(i).getLabel() + "\">"
672                + list.get(i).getLabel() + "</a>");
673            buf.append("  </li>");
674          } else {
675            buf.append("<li class=\"nav-section sticky\">");
676            buf.append("<div class=\"nav-section-header empty\">");
677            buf.append("<a href=\"#\" onclick=\"return false;\" title=\""
678                + list.get(i).getLabel() + "\">"
679                + list.get(i).getLabel() + "/</a>");
680            buf.append("</div>");
681            buf.append("<ul>");
682            list.get(i).renderChildrenToc(buf);
683          }
684        }
685        buf.append("</ul>");
686        buf.append("</li>");
687      }
688    }
689
690    /**
691    * Node getters and setters
692    */
693    public String getLabel() {
694      return mLabel;
695    }
696
697    public void setLabel(String label) {
698       mLabel = label;
699    }
700
701    public String getLink() {
702      return mLink;
703    }
704
705    public void setLink(String ref) {
706       mLink = ref;
707    }
708
709    public String getGroup() {
710      return mGroup;
711    }
712
713    public void setGroup(String group) {
714      mGroup = group;
715    }
716
717    public List<Node> getChildren() {
718        return mChildren;
719    }
720
721    public void setChildren(List<Node> node) {
722        mChildren = node;
723    }
724
725    public String getType() {
726      return mType;
727    }
728
729    public void setType(String type) {
730      mType = type;
731    }
732  }
733}
734