1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.doclava;
18
19import com.google.clearsilver.jsilver.data.Data;
20
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.List;
24import java.util.SortedMap;
25import java.util.TreeMap;
26
27public class NavTree {
28
29  public static void writeNavTree(String dir, String refPrefix) {
30    List<Node> children = new ArrayList<Node>();
31    for (PackageInfo pkg : Doclava.choosePackages()) {
32      children.add(makePackageNode(pkg));
33    }
34    Node node = new Node("Reference", dir + refPrefix + "packages.html", children, null);
35
36    StringBuilder buf = new StringBuilder();
37    if (false) {
38      // if you want a root node
39      buf.append("[");
40      node.render(buf);
41      buf.append("]");
42    } else {
43      // if you don't want a root node
44      node.renderChildren(buf);
45    }
46
47    Data data = Doclava.makeHDF();
48    data.setValue("reference_tree", buf.toString());
49    if (refPrefix == "gms-"){
50      ClearPage.write(data, "gms_navtree_data.cs", "gms_navtree_data.js");
51    } else if (refPrefix == "gcm-"){
52      ClearPage.write(data, "gcm_navtree_data.cs", "gcm_navtree_data.js");
53    } else if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS && (Doclava.libraryRoot != null)) {
54        ClearPage.write(data, "navtree_data.cs", dir + Doclava.libraryRoot
55          + "navtree_data.js");
56    } else {
57      ClearPage.write(data, "navtree_data.cs", "navtree_data.js");
58    }
59  }
60
61  /**
62   * Write the YAML formatted navigation tree.
63   * This is intended to replace writeYamlTree(), but for now requires an explicit opt-in via
64   * the yamlV2 flag in the doclava command. This version creates a yaml file with all classes,
65   * interface, exceptions, etc. separated into collapsible groups.
66   */
67  public static void writeYamlTree2(String dir, String fileName){
68    List<Node> children = new ArrayList<Node>();
69    for (PackageInfo pkg : Doclava.choosePackages()) {
70      children.add(makePackageNode(pkg));
71    }
72    Node node = new Node("Reference", Doclava.ensureSlash(dir) + "packages.html", children, null);
73    StringBuilder buf = new StringBuilder();
74
75    node.renderChildrenYaml(buf, 0);
76
77    Data data = Doclava.makeHDF();
78    data.setValue("reference_tree", buf.toString());
79
80    if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS && (Doclava.libraryRoot != null)) {
81      dir = Doclava.ensureSlash(dir) + Doclava.libraryRoot;
82    }
83
84    data.setValue("docs.classes.link", Doclava.ensureSlash(dir) + "classes.html");
85    data.setValue("docs.packages.link", Doclava.ensureSlash(dir) + "packages.html");
86
87    ClearPage.write(data, "yaml_navtree2.cs", Doclava.ensureSlash(dir) + fileName);
88
89  }
90
91
92  /**
93   * Write the YAML formatted navigation tree (legacy version).
94   * This creates a yaml file with package names followed by all
95   * classes, interfaces, exceptions, etc. But they are not separated by classes, interfaces, etc.
96   * It also nests any nested classes under the parent class, instead of listing them as siblings.
97   * @see "http://yaml.org/"
98   */
99  public static void writeYamlTree(String dir, String fileName){
100    Data data = Doclava.makeHDF();
101    Collection<ClassInfo> classes = Converter.rootClasses();
102
103    SortedMap<String, Object> sorted = new TreeMap<String, Object>();
104    for (ClassInfo cl : classes) {
105      if (cl.isHiddenOrRemoved()) {
106        continue;
107      }
108      sorted.put(cl.qualifiedName(), cl);
109
110      PackageInfo pkg = cl.containingPackage();
111      String name;
112      if (pkg == null) {
113        name = "";
114      } else {
115        name = pkg.name();
116      }
117      sorted.put(name, pkg);
118    }
119
120    data = makeYamlHDF(sorted, "docs.pages", data);
121
122    if (Doclava.USE_DEVSITE_LOCALE_OUTPUT_PATHS && (Doclava.libraryRoot != null)) {
123      dir = Doclava.ensureSlash(dir) + Doclava.libraryRoot;
124    }
125
126    data.setValue("docs.classes.link", Doclava.ensureSlash(dir) + "classes.html");
127    data.setValue("docs.packages.link", Doclava.ensureSlash(dir) + "packages.html");
128
129    ClearPage.write(data, "yaml_navtree.cs", Doclava.ensureSlash(dir) + fileName);
130  }
131
132  public static Data makeYamlHDF(SortedMap<String, Object> sorted, String base, Data data) {
133
134    String key = "docs.pages.";
135    int i = 0;
136    for (String s : sorted.keySet()) {
137      Object o = sorted.get(s);
138
139      if (o instanceof PackageInfo) {
140        PackageInfo pkg = (PackageInfo) o;
141
142        data.setValue("docs.pages." + i + ".id", "" + i);
143        data.setValue("docs.pages." + i + ".label", pkg.name());
144        data.setValue("docs.pages." + i + ".shortname", "API");
145        data.setValue("docs.pages." + i + ".apilevel", pkg.getSince());
146        data.setValue("docs.pages." + i + ".link", pkg.htmlPage());
147        data.setValue("docs.pages." + i + ".type", "package");
148      } else if (o instanceof ClassInfo) {
149        ClassInfo cl = (ClassInfo) o;
150
151        // skip classes that are the child of another class, recursion will handle those.
152        if (cl.containingClass() == null) {
153
154          data.setValue("docs.pages." + i + ".id", "" + i);
155          data = makeYamlHDF(cl, "docs.pages."+i, data);
156        }
157      }
158
159      i++;
160    }
161
162    return data;
163  }
164
165  public static Data makeYamlHDF(ClassInfo cl, String base, Data data) {
166    data.setValue(base + ".label", cl.name());
167    data.setValue(base + ".shortname", cl.name().substring(cl.name().lastIndexOf(".")+1));
168    data.setValue(base + ".link", cl.htmlPage());
169    data.setValue(base + ".type", cl.kind());
170
171    if (cl.innerClasses().size() > 0) {
172      int j = 0;
173      for (ClassInfo cl2 : cl.innerClasses()) {
174        if (cl2.isHiddenOrRemoved()) {
175          continue;
176        }
177        data = makeYamlHDF(cl2, base + ".children." + j, data);
178        j++;
179      }
180    }
181
182    return data;
183  }
184
185  private static Node makePackageNode(PackageInfo pkg) {
186    List<Node> children = new ArrayList<Node>();
187
188    addClassNodes(children, "Annotations", pkg.annotations());
189    addClassNodes(children, "Interfaces", pkg.interfaces());
190    addClassNodes(children, "Classes", pkg.ordinaryClasses());
191    addClassNodes(children, "Enums", pkg.enums());
192    addClassNodes(children, "Exceptions", pkg.exceptions());
193    addClassNodes(children, "Errors", pkg.errors());
194
195    return new Node(pkg.name(), pkg.htmlPage(), children, pkg.getSince());
196  }
197
198  private static void addClassNodes(List<Node> parent, String label, ClassInfo[] classes) {
199    List<Node> children = new ArrayList<Node>();
200
201    for (ClassInfo cl : classes) {
202      if (cl.checkLevel()) {
203        children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince(), cl.getArtifact()));
204      }
205    }
206
207    if (children.size() > 0) {
208      parent.add(new Node(label, null, children, null));
209    }
210  }
211
212  private static class Node {
213    private String mLabel;
214    private String mLink;
215    List<Node> mChildren;
216    private String mSince;
217    private String mArtifact;
218
219    Node(String label, String link, List<Node> children, String since) {
220      this(label, link, children, since, null);
221    }
222
223    Node(String label, String link, List<Node> children, String since, String artifact) {
224      mLabel = label;
225      mLink = link;
226      mChildren = children;
227      mSince = since;
228      mArtifact = artifact;
229    }
230
231    static void renderString(StringBuilder buf, String s) {
232      if (s == null) {
233        buf.append("null");
234      } else {
235        buf.append('"');
236        final int N = s.length();
237        for (int i = 0; i < N; i++) {
238          char c = s.charAt(i);
239          if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
240            buf.append(c);
241          } else {
242            buf.append("\\u");
243            for (int j = 0; i < 4; i++) {
244              char x = (char) (c & 0x000f);
245              if (x >= 10) {
246                x = (char) (x - 10 + 'a');
247              } else {
248                x = (char) (x + '0');
249              }
250              buf.append(x);
251              c >>= 4;
252            }
253          }
254        }
255        buf.append('"');
256      }
257    }
258
259    void renderChildren(StringBuilder buf) {
260      List<Node> list = mChildren;
261      if (list == null || list.size() == 0) {
262        // We output null for no children. That way empty lists here can just
263        // be a byproduct of how we generate the lists.
264        buf.append("null");
265      } else {
266        buf.append("[ ");
267        final int N = list.size();
268        for (int i = 0; i < N; i++) {
269          list.get(i).render(buf);
270          if (i != N - 1) {
271            buf.append(", ");
272          }
273        }
274        buf.append(" ]\n");
275      }
276    }
277
278    void render(StringBuilder buf) {
279      buf.append("[ ");
280      renderString(buf, mLabel);
281      buf.append(", ");
282      renderString(buf, mLink);
283      buf.append(", ");
284      renderChildren(buf);
285      buf.append(", ");
286      renderString(buf, mSince);
287      buf.append(", ");
288      renderString(buf, mArtifact);
289      buf.append(" ]");
290    }
291
292
293    // YAML VERSION
294
295
296    static void renderStringYaml(StringBuilder buf, String s) {
297      if (s != null) {
298        final int N = s.length();
299        for (int i = 0; i < N; i++) {
300          char c = s.charAt(i);
301          if (c >= ' ' && c <= '~' && c != '"' && c != '\\') {
302            buf.append(c);
303          } else {
304            buf.append("\\u");
305            for (int j = 0; i < 4; i++) {
306              char x = (char) (c & 0x000f);
307              if (x >= 10) {
308                x = (char) (x - 10 + 'a');
309              } else {
310                x = (char) (x + '0');
311              }
312              buf.append(x);
313              c >>= 4;
314            }
315          }
316        }
317      }
318    }
319    void renderChildrenYaml(StringBuilder buf, int depth) {
320      List<Node> list = mChildren;
321      if (list != null && list.size() > 0) {
322        if (depth > 0) {
323          buf.append("\n\n" + getIndent(depth));
324          buf.append("section:");
325        }
326        final int N = list.size();
327        for (int i = 0; i < N; i++) {
328          // get each child Node and render it
329          list.get(i).renderYaml(buf, depth);
330        }
331        // Extra line break after each "section"
332        buf.append("\n");
333      }
334    }
335    void renderYaml(StringBuilder buf, int depth) {
336      buf.append("\n" + getIndent(depth));
337      buf.append("- title: \"");
338      renderStringYaml(buf, mLabel);
339      buf.append("\"");
340      // Add link path, if it exists (the class/interface toggles don't have links)
341      if (mLink != null) {
342        buf.append("\n" + getIndent(depth));
343        buf.append("  path: ");
344        renderStringYaml(buf, "/" + mLink);
345        // add the API level info only if we have it
346        if (mSince != null) {
347          buf.append("\n" + getIndent(depth));
348          buf.append("  version_added: ");
349          renderStringYaml(buf, "'" + mSince + "'");
350        }
351      }
352      // try rendering child Nodes
353      renderChildrenYaml(buf, depth + 1);
354    }
355    String getIndent(int depth) {
356      String spaces = "";
357      for (int i = 0; i < depth; i++) {
358        spaces += "  ";
359      }
360      return spaces;
361    }
362  }
363}