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 
SampleCode(String source, String dest, String title)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   */
setSamplesTOC(boolean offlineMode)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   */
writeSamplesFiles(boolean offlineMode)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   */
setProjectStructure(List<Node> parent, File dir, String relative)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   */
writeProjectDirectory(File dir, String relative, Boolean recursed, Data hdf, String newkey)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   */
writeSampleIndexCs(Data hdf, File dir, Node tnode, boolean writeFiles)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   */
writeProjectStructure(String dir, Data hdf)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   */
setParentDirs(Data hdf, String subdir, String name, Boolean isFile)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   */
writePage(File f, String out, String subdir, Data hdf)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   */
writeImageVideoPage(File f, String out, String subdir, String resourceType, boolean browsable)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   */
writeSamplesNavTree(List<Node> tnode, List<Node> groupnodes)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   */
appendNodeGroups(Node gNode, List<Node> groupnodes)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   */
squashNodes(List<Node> tnode)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 
convertExtension(String s, String ext)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 
inList(String s, String[] list)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   */
mapTypes(String name)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   */
isValidFiletype(String name)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 
Node(Builder builder)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;
setLabel(String mLabel)592       public Builder setLabel(String mLabel) { this.mLabel = mLabel; return this;}
setLink(String mLink)593       public Builder setLink(String mLink) { this.mLink = mLink; return this;}
setGroup(String mGroup)594       public Builder setGroup(String mGroup) { this.mGroup = mGroup; return this;}
setChildren(List<Node> mChildren)595       public Builder setChildren(List<Node> mChildren) { this.mChildren = mChildren; return this;}
setType(String mType)596       public Builder setType(String mType) { this.mType = mType; return this;}
build()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     */
renderGroupNodesTOC(StringBuilder buf)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     */
renderProjectNodesTOC(StringBuilder buf)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     */
renderChildrenToc(StringBuilder buf)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     */
getLabel()693     public String getLabel() {
694       return mLabel;
695     }
696 
setLabel(String label)697     public void setLabel(String label) {
698        mLabel = label;
699     }
700 
getLink()701     public String getLink() {
702       return mLink;
703     }
704 
setLink(String ref)705     public void setLink(String ref) {
706        mLink = ref;
707     }
708 
getGroup()709     public String getGroup() {
710       return mGroup;
711     }
712 
setGroup(String group)713     public void setGroup(String group) {
714       mGroup = group;
715     }
716 
getChildren()717     public List<Node> getChildren() {
718         return mChildren;
719     }
720 
setChildren(List<Node> node)721     public void setChildren(List<Node> node) {
722         mChildren = node;
723     }
724 
getType()725     public String getType() {
726       return mType;
727     }
728 
setType(String type)729     public void setType(String type) {
730       mType = type;
731     }
732   }
733 }
734