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     String prefixRoot = Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS ? "en/" : "";
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 baseRelative = relative;
204         if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
205           // don't nest root path
206           baseRelative = baseRelative.replaceFirst("^en/", "");
207         }
208         String path = baseRelative + name;
209         type = mapTypes(name);
210         link = convertExtension(path, Doclava.htmlExtension);
211         if (inList(path, IMAGES)) {
212           type = "img";
213           if (f.length() < MAX_FILE_SIZE_BYTES) {
214             ClearPage.copyFile(false, f, prefixRoot + path, false);
215             writeImageVideoPage(f, convertExtension(prefixRoot + path, Doclava.htmlExtension),
216                 relative, type, true);
217           } else {
218             writeImageVideoPage(f, convertExtension(prefixRoot + path, Doclava.htmlExtension),
219                 relative, type, false);
220           }
221           hdf.setValue(key + i + ".Type", "img");
222           hdf.setValue(key + i + ".Name", name);
223           hdf.setValue(key + i + ".Href", link);
224           hdf.setValue(key + i + ".RelPath", relative);
225         } else if (inList(path, VIDEOS)) {
226           type = "video";
227           if (f.length() < MAX_FILE_SIZE_BYTES) {
228             ClearPage.copyFile(false, f, prefixRoot + path, false);
229             writeImageVideoPage(f, convertExtension(prefixRoot + path, Doclava.htmlExtension),
230                 relative, type, true);
231           } else {
232             writeImageVideoPage(f, convertExtension(prefixRoot + path, Doclava.htmlExtension),
233                 relative, type, false);
234           }
235           hdf.setValue(key + i + ".Type", "video");
236           hdf.setValue(key + i + ".Name", name);
237           hdf.setValue(key + i + ".Href", link);
238           hdf.setValue(key + i + ".RelPath", relative);
239         } else if (inList(path, TEMPLATED)) {
240           writePage(f, convertExtension(prefixRoot + path, Doclava.htmlExtension), relative, hdf);
241           hdf.setValue(key + i + ".Type", type);
242           hdf.setValue(key + i + ".Name", name);
243           hdf.setValue(key + i + ".Href", link);
244           hdf.setValue(key + i + ".RelPath", relative);
245         }
246         i++;
247       } else if (f.isDirectory()) {
248         List<Node> mchildren = new ArrayList<Node>();
249         type = "dir";
250         String dirpath = relative + name;
251         link = dirpath + "/index" + Doclava.htmlExtension;
252          String hdfkeyName = (key + i + ".Name");
253          String hdfkeyType = (key + i + ".Type");
254          String hdfkeyHref = (key + i + ".Href");
255         hdf.setValue(hdfkeyName, name);
256         hdf.setValue(hdfkeyType, type);
257         hdf.setValue(hdfkeyHref, relative + name + "/" + "index" + Doclava.htmlExtension);
258         writeProjectDirectory(f, relative + name + "/", true, hdf, (key + i));
259         i++;
260       }
261     }
262 
263     setParentDirs(hdf, relative, name, false);
264     //Generate an index.html page for each dir being processed
265     if (FULL_TREE_NAVIGATION) {
266       ClearPage.write(hdf, "sampleindex.cs", prefixRoot + relative + "/index" + Doclava.htmlExtension);
267     }
268   }
269 
270   /**
271   * Processes a templated project index page from _index.jd in a project root.
272   * Each sample project must have an index, and each index locally defines it's own
273   * page.tags and sample.group cs vars. This method takes a SC node on input, reads
274   * any local vars from the _index.jd, optionally generates an html file to out,
275   * then updates the SC node with the page vars and returns it to the caller.
276   *
277   * @param hdf The data source to read/write for this index file.
278   * @param dir The sample project root directory.
279   * @param tnode A Node to serve as the project's root node.
280   * @param writeFiles If true, generates output files only. If false, collects
281   *        metadata only.
282   * @return The tnode root with any metadata/child Nodes appended.
283   */
writeSampleIndexCs(Data hdf, File dir, Node tnode, boolean writeFiles)284   public Node writeSampleIndexCs(Data hdf, File dir, Node tnode, boolean writeFiles) {
285 
286     String filename = dir.getAbsolutePath() + "/_index.jd";
287     String mGroup = "";
288     File f = new File(filename);
289     String rel = dir.getPath();
290     String prefixRoot = Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS ? "en/" : "";
291     if (writeFiles) {
292 
293       hdf.setValue("samples", "true");
294       //set any default page variables for root index
295       hdf.setValue("page.title", mProjectDir);
296       hdf.setValue("projectDir", mProjectDir);
297       hdf.setValue("projectTitle", mTitle);
298       //add the download/project links to the landing pages.
299       hdf.setValue("samplesProjectIndex", "true");
300       if (!f.isFile()) {
301         //The directory didn't have an _index.jd, so create a stub.
302         ClearPage.write(hdf, "sampleindex.cs", prefixRoot + mDest + "index" + Doclava.htmlExtension);
303       } else {
304         DocFile.writePage(filename, rel, prefixRoot + mDest + "index" + Doclava.htmlExtension, hdf);
305       }
306     } else if (f.isFile()) {
307       //gather metadata for toc and jd_lists_unified
308       DocFile.getPageMetadata(filename, hdf);
309       mGroup = hdf.getValue("sample.group", "");
310       if (!"".equals(mGroup)) {
311         tnode.setGroup(hdf.getValue("sample.group", ""));
312       } else {
313         //Errors.error(Errors.INVALID_SAMPLE_INDEX, null, "Sample " + mProjectDir
314         //          + ": Root _index.jd must be present and must define sample.group"
315         //          + " tag. Please see ... for details.");
316       }
317     }
318     return tnode;
319   }
320 
321   /**
322   * Sets metadata for managing html output and generates the project view page
323   * for a project.
324   *
325   * @param dir The project root dir.
326   * @param hdf The data to read/write for files in this project.
327   */
writeProjectStructure(String dir, Data hdf)328   public void writeProjectStructure(String dir, Data hdf) {
329     String prefixRoot = Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS ? "en/" : "";
330     hdf.setValue("projectStructure", "true");
331     hdf.setValue("projectDir", mProjectDir);
332     hdf.setValue("page.title", mProjectDir + " Structure");
333     hdf.setValue("projectTitle", mTitle);
334     ClearPage.write(hdf, "sampleindex.cs", prefixRoot + mDest + "project" + Doclava.htmlExtension);
335     hdf.setValue("projectStructure", "");
336   }
337 
338   /**
339   * Keeps track of each file's parent dirs. Used for generating path breadcrumbs in html.
340   *
341   * @param dir The data to read/write for this file.
342   * @param hdf The relative path for this file, from samples root.
343   * @param subdir The relative path for this file, from samples root.
344   * @param name The name of the file (minus extension).
345   * @param isFile Whether this is a file (not a dir).
346   */
setParentDirs(Data hdf, String subdir, String name, Boolean isFile)347   Data setParentDirs(Data hdf, String subdir, String name, Boolean isFile) {
348     if (FULL_TREE_NAVIGATION) {
349       hdf.setValue("linkfyPathCrumb", "");
350     }
351     int iter;
352     hdf.removeTree("parentdirs");
353     String s = subdir;
354     String urlParts[] = s.split("/");
355     int n, l = 1;
356     for (iter=1; iter < urlParts.length; iter++) {
357       n = iter-1;
358       hdf.setValue("parentdirs." + n + ".Name", urlParts[iter]);
359       hdf.setValue("parentdirs." + n + ".Link", subdir + "index" + Doclava.htmlExtension);
360     }
361     return hdf;
362   }
363 
364   /**
365   * Writes a templated source code file to out.
366   */
writePage(File f, String out, String subdir, Data hdf)367   public void writePage(File f, String out, String subdir, Data hdf) {
368     String name = f.getName();
369     String path = f.getPath();
370     String data = SampleTagInfo.readFile(new SourcePositionInfo(path, -1, -1), path,
371         "sample code", true, true, true, true);
372     data = Doclava.escape(data);
373 
374     String relative = subdir.replaceFirst("samples/", "");
375     setParentDirs(hdf, subdir, name, true);
376     hdf.setValue("projectTitle", mTitle);
377     hdf.setValue("projectDir", mProjectDir);
378     hdf.setValue("page.title", name);
379     hdf.setValue("subdir", subdir);
380     hdf.setValue("relative", relative);
381     hdf.setValue("realFile", name);
382     hdf.setValue("fileContents", data);
383     hdf.setValue("resTag", "sample");
384 
385     ClearPage.write(hdf, "sample.cs", out);
386   }
387 
388   /**
389   * Writes a templated image or video file to out.
390   */
writeImageVideoPage(File f, String out, String subdir, String resourceType, boolean browsable)391   public void writeImageVideoPage(File f, String out, String subdir,
392         String resourceType, boolean browsable) {
393     Data hdf = Doclava.makeHDF();
394     if (Doclava.samplesNavTree != null) {
395       hdf.setValue("samples_toc_tree", Doclava.samplesNavTree.getValue("samples_toc_tree", ""));
396     }
397     hdf.setValue("samples", "true");
398 
399     String name = f.getName();
400     if (!browsable) {
401       hdf.setValue("noDisplay", "true");
402     }
403     setParentDirs(hdf, subdir, name, true);
404     hdf.setValue("samples", "true");
405     hdf.setValue("page.title", name);
406     hdf.setValue("projectTitle", mTitle);
407     hdf.setValue("projectDir", mProjectDir);
408     hdf.setValue("subdir", subdir);
409     hdf.setValue("resType", resourceType);
410     hdf.setValue("realFile", name);
411     ClearPage.write(hdf, "sample.cs", out);
412   }
413 
414   /**
415   * Given a node containing sample code projects and a node containing all valid
416   * group nodes, extract project nodes from tnode and append them to the group node
417   * that matches their sample.group metadata.
418   *
419   * @param tnode A list of nodes containing sample code projects.
420   * @param groupnodes A list of nodes that represent the valid sample groups.
421   * @return The groupnodes list with all projects appended properly to their
422   *         associated sample groups.
423   */
writeSamplesNavTree(List<Node> tnode, List<Node> groupnodes)424   public static void writeSamplesNavTree(List<Node> tnode, List<Node> groupnodes) {
425 
426     Node node = new Node.Builder().setLabel("Samples").setLink(ClearPage.toroot
427         + "samples/index" + Doclava.htmlExtension).setChildren(tnode).build();
428 
429     if (groupnodes != null) {
430       for (int i = 0; i < tnode.size(); i++) {
431         if (tnode.get(i) != null) {
432           groupnodes = appendNodeGroups(tnode.get(i), groupnodes);
433         }
434       }
435       for (int n = 0; n < groupnodes.size(); n++) {
436         if (groupnodes.get(n).getChildren() == null) {
437           groupnodes.remove(n);
438           n--;
439         } else {
440           Collections.sort(groupnodes.get(n).getChildren(), byLabel);
441         }
442       }
443       node.setChildren(groupnodes);
444     }
445 
446     StringBuilder buf = new StringBuilder();
447     if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
448       node.renderGroupNodesTOCYaml(buf, "", false);
449     } else {
450       node.renderGroupNodesTOC(buf);
451     }
452     if (Doclava.samplesNavTree != null) {
453       Doclava.samplesNavTree.setValue("samples_toc_tree", buf.toString());
454       if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS) {
455         ClearPage.write(Doclava.samplesNavTree, "samples_navtree_data.cs", "en/samples/_book.yaml");
456       }
457     }
458   }
459 
460   /**
461   * For a given project root node, get the group and then iterate the list of valid
462   * groups looking for a match. If found, append the project to that group node.
463   * Samples that reference a valid sample group tag are added to a list for that
464   * group. Samples declare a sample.group tag in their _index.jd files.
465   */
appendNodeGroups(Node gNode, List<Node> groupnodes)466   private static List<Node> appendNodeGroups(Node gNode, List<Node> groupnodes) {
467     List<Node> mgrouplist = new ArrayList<Node>();
468     for (int i = 0; i < groupnodes.size(); i++) {
469       if (gNode.getGroup().equals(groupnodes.get(i).getLabel())) {
470         if (groupnodes.get(i).getChildren() == null) {
471           mgrouplist.add(gNode);
472           groupnodes.get(i).setChildren(mgrouplist);
473         } else {
474           groupnodes.get(i).getChildren().add(gNode);
475         }
476         break;
477       }
478     }
479     return groupnodes;
480   }
481 
482   /**
483   * Sorts an array of files by type and name (alpha), with manifest always at top.
484   */
485   Comparator<File> byTypeAndName = new Comparator<File>() {
486     public int compare (File one, File other) {
487       if (one.isDirectory() && !other.isDirectory()) {
488         return 1;
489       } else if (!one.isDirectory() && other.isDirectory()) {
490         return -1;
491       } else if ("AndroidManifest.xml".equals(one.getName())) {
492         return -1;
493       } else {
494         return one.compareTo(other);
495       }
496     }
497   };
498 
499   /**
500   * Sorts a list of Nodes by label.
501   */
502   public static Comparator<Node> byLabel = new Comparator<Node>() {
503     public int compare(Node one, Node other) {
504       return one.getLabel().compareTo(other.getLabel());
505     }
506   };
507 
508   /**
509   * Concatenates dirs that only hold dirs, to simplify nav tree
510   */
squashNodes(List<Node> tnode)511   public static List<Node> squashNodes(List<Node> tnode) {
512     List<Node> list = tnode;
513 
514     for(int i = 0; i < list.size(); ++i) {
515       if (("dir".equals(list.get(i).getType())) &&
516           (list.size() == 1) &&
517           (list.get(i).getChildren().get(0).getChildren() != null)) {
518         String thisLabel = list.get(i).getLabel();
519         String childLabel =  list.get(i).getChildren().get(0).getLabel();
520         String newLabel = thisLabel + "/" + childLabel;
521         list.get(i).setLabel(newLabel);
522         list.get(i).setChildren(list.get(i).getChildren().get(0).getChildren());
523       } else {
524         continue;
525       }
526     }
527     return list;
528   }
529 
convertExtension(String s, String ext)530   public static String convertExtension(String s, String ext) {
531     return s.substring(0, s.lastIndexOf('.')) + ext;
532   }
533 
534   /**
535   * Whitelists of valid image/video and source code types.
536   */
537   public static String[] IMAGES = {".png", ".jpg", ".gif"};
538   public static String[] VIDEOS = {".mp4", ".ogv", ".webm"};
539   public static String[] TEMPLATED = {".java", ".xml", ".aidl", ".rs",".txt", ".TXT"};
540 
inList(String s, String[] list)541   public static boolean inList(String s, String[] list) {
542     for (String t : list) {
543       if (s.endsWith(t)) {
544         return true;
545       }
546     }
547     return false;
548   }
549 
550   /**
551   * Maps filenames to a set of generic types. Used for displaying files/dirs
552   * in the project view page.
553   */
mapTypes(String name)554   public static String mapTypes(String name) {
555     String type = name.substring(name.lastIndexOf('.') + 1, name.length());
556     if ("xml".equals(type) || "java".equals(type)) {
557       if ("AndroidManifest.xml".equals(name)) type = "manifest";
558       return type;
559     } else {
560       return type = "file";
561     }
562   }
563 
564   /**
565   * Validates a source file from a project against restrictions to determine
566   * whether to include the file in the browsable project output.
567   */
isValidFiletype(String name)568   public boolean isValidFiletype(String name) {
569     if (name.startsWith(".") ||
570         name.startsWith("_") ||
571         "default.properties".equals(name) ||
572         "build.properties".equals(name) ||
573         name.endsWith(".ttf") ||
574         name.endsWith(".gradle") ||
575         name.endsWith(".bat") ||
576         "Android.mk".equals(name)) {
577       return false;
578     } else {
579       return true;
580     }
581   }
582 
583   /**
584   * SampleCode variant of NavTree node.
585   */
586   public static class Node {
587     private String mLabel;
588     private String mLink;
589     private String mGroup; // from sample.group in _index.jd
590     private List<Node> mChildren;
591     private String mType;
592 
Node(Builder builder)593     private Node(Builder builder) {
594       mLabel = builder.mLabel;
595       mLink = builder.mLink;
596       mGroup = builder.mGroup;
597       mChildren = builder.mChildren;
598       mType = builder.mType;
599     }
600 
601     public static class Builder {
602       private String mLabel, mLink, mGroup, mType;
603       private List<Node> mChildren = null;
setLabel(String mLabel)604       public Builder setLabel(String mLabel) { this.mLabel = mLabel; return this;}
setLink(String mLink)605       public Builder setLink(String mLink) { this.mLink = mLink; return this;}
setGroup(String mGroup)606       public Builder setGroup(String mGroup) { this.mGroup = mGroup; return this;}
setChildren(List<Node> mChildren)607       public Builder setChildren(List<Node> mChildren) { this.mChildren = mChildren; return this;}
setType(String mType)608       public Builder setType(String mType) { this.mType = mType; return this;}
build()609       public Node build() {return new Node(this);}
610     }
611 
612     /**
613     * Renders browsable sample groups and projects to a _book.yaml file, starting
614     * from the group nodes and then rendering their project nodes and finally their
615     * child dirs and files.
616     */
renderGroupNodesTOCYaml(StringBuilder buf, String indent, Boolean isChild)617     void renderGroupNodesTOCYaml(StringBuilder buf, String indent, Boolean isChild) {
618       List<Node> list = mChildren;
619       if (list == null || list.size() == 0) {
620         return;
621       } else {
622         final int n = list.size();
623         if (indent.length() > 0) {
624           buf.append(indent + "section:\n");
625         } // else append 'toc:\n' if needed
626         for (int i = 0; i < n; i++) {
627           if (isChild == true && list.get(i).getChildren() != null) {
628             buf.append(indent + "- title: " + list.get(i).getLabel() + "/\n");
629             if (list.get(i).getLink().indexOf(".html") > -1) {
630               buf.append(indent + "  path: " + list.get(i).getLink() + "\n");
631               buf.append(indent + "  path_attributes:\n");
632               buf.append(indent + "  - name: title\n");
633               buf.append(indent + "    value: " + list.get(i).getLabel() + "\n");
634             } else {
635               buf.append(indent + "  path: \"#\"\n");
636               buf.append(indent + "  path_attributes:\n");
637               buf.append(indent + "  - name: onclick\n");
638               buf.append(indent + "    value: return false;\n");
639               buf.append(indent + "  - name: title\n");
640               buf.append(indent + "    value: " + list.get(i).getLabel() + "\n");
641             }
642           } else {
643             String xmlToHtmlPath = list.get(i).getLink().replace(".xml", ".html");
644             buf.append(indent + "- title: " + list.get(i).getLabel() + "\n");
645             buf.append(indent + "  path: " + xmlToHtmlPath + "\n");
646           }
647           if (list.get(i).getChildren() != null) {
648             list.get(i).renderGroupNodesTOCYaml(buf, indent + "  ", true);
649           }
650         }
651       }
652     }
653 
654     /**
655     * Renders browsable sample groups and projects to an html list, starting
656     * from the group nodes and then rendering their project nodes and finally their
657     * child dirs and files.
658     */
renderGroupNodesTOC(StringBuilder buf)659     void renderGroupNodesTOC(StringBuilder buf) {
660       List<Node> list = mChildren;
661       if (list == null || list.size() == 0) {
662         return;
663       } else {
664         final int n = list.size();
665         for (int i = 0; i < n; i++) {
666           if (list.get(i).getChildren() == null) {
667             continue;
668           } else {
669             buf.append("<li class=\"nav-section\">");
670             buf.append("<div class=\"nav-section-header\">");
671             buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
672                 + list.get(i).getLabel() + "\">"
673                 + list.get(i).getLabel() + "</a>");
674             buf.append("</div>");
675             buf.append("<ul>");
676             list.get(i).renderProjectNodesTOC(buf);
677           }
678         }
679         buf.append("</ul>");
680         buf.append("</li>");
681       }
682     }
683 
684     /**
685     * Renders a list of sample code projects associated with a group node.
686     */
renderProjectNodesTOC(StringBuilder buf)687     void renderProjectNodesTOC(StringBuilder buf) {
688       List<Node> list = mChildren;
689       if (list == null || list.size() == 0) {
690         return;
691       } else {
692         final int n = list.size();
693         for (int i = 0; i < n; i++) {
694           if (list.get(i).getChildren() == null) {
695             continue;
696           } else {
697             buf.append("<li class=\"nav-section\">");
698             buf.append("<div class=\"nav-section-header\">");
699             buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
700                 + list.get(i).getLabel() + "\">"
701                 + list.get(i).getLabel() + "</a>");
702             buf.append("</div>");
703             buf.append("<ul>");
704             list.get(i).renderChildrenToc(buf);
705           }
706         }
707         buf.append("</ul>");
708         buf.append("</li>");
709       }
710     }
711 
712     /**
713     * Renders child dirs and files associated with a project node.
714     */
renderChildrenToc(StringBuilder buf)715     void renderChildrenToc(StringBuilder buf) {
716       List<Node> list = mChildren;
717       if (list == null || list.size() == 0) {
718         buf.append("null");
719       } else {
720         final int n = list.size();
721         for (int i = 0; i < n; i++) {
722           if (list.get(i).getChildren() == null) {
723             buf.append("<li>");
724             buf.append("<a href=\"" + list.get(i).getLink() + "\" title=\""
725                 + list.get(i).getLabel() + "\">"
726                 + list.get(i).getLabel() + "</a>");
727             buf.append("  </li>");
728           } else {
729             buf.append("<li class=\"nav-section sticky\">");
730             buf.append("<div class=\"nav-section-header empty\">");
731             buf.append("<a href=\"#\" onclick=\"return false;\" title=\""
732                 + list.get(i).getLabel() + "\">"
733                 + list.get(i).getLabel() + "/</a>");
734             buf.append("</div>");
735             buf.append("<ul>");
736             list.get(i).renderChildrenToc(buf);
737           }
738         }
739         buf.append("</ul>");
740         buf.append("</li>");
741       }
742     }
743 
744     /**
745     * Node getters and setters
746     */
getLabel()747     public String getLabel() {
748       return mLabel;
749     }
750 
setLabel(String label)751     public void setLabel(String label) {
752        mLabel = label;
753     }
754 
getLink()755     public String getLink() {
756       return mLink;
757     }
758 
setLink(String ref)759     public void setLink(String ref) {
760        mLink = ref;
761     }
762 
getGroup()763     public String getGroup() {
764       return mGroup;
765     }
766 
setGroup(String group)767     public void setGroup(String group) {
768       mGroup = group;
769     }
770 
getChildren()771     public List<Node> getChildren() {
772         return mChildren;
773     }
774 
setChildren(List<Node> node)775     public void setChildren(List<Node> node) {
776         mChildren = node;
777     }
778 
getType()779     public String getType() {
780       return mType;
781     }
782 
setType(String type)783     public void setType(String type) {
784       mType = type;
785     }
786   }
787 }
788