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}