Home | History | Annotate | Download | only in doclava
      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 
     17 package com.google.doclava;
     18 
     19 import java.util.Arrays;
     20 import java.util.ArrayList;
     21 import java.util.Collections;
     22 import java.util.Comparator;
     23 import java.util.List;
     24 import java.util.regex.Pattern;
     25 import java.util.regex.Matcher;
     26 import java.io.File;
     27 
     28 import 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 */
     34 public 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