1/*
2 * Copyright (C) 2015 The Android Open Source Project
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#include <iostream>
18#include <sstream>
19
20#include "Generator.h"
21#include "Specification.h"
22#include "Utilities.h"
23
24using namespace std;
25
26struct DetailedFunctionEntry {
27    VersionInfo info;
28    string htmlDeclaration;
29};
30
31static const char OVERVIEW_HTML_FILE_NAME[] = "overview.html";
32static const char OVERVIEW_JD_FILE_NAME[] = "overview.jd";
33static const char INDEX_HTML_FILE_NAME[] = "index.html";
34static const char INDEX_JD_FILE_NAME[] = "index.jd";
35
36static void writeHeader(GeneratedFile* file, bool forVerification, const string& title) {
37    if (forVerification) {
38        *file << "<!DOCTYPE html>\n";
39        *file << "<!-- " << AUTO_GENERATED_WARNING << "-->\n";
40        *file << "<html><head>\n"
41                 "<title>RenderScript Reference</title>\n"
42                 "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>\n"
43                 "<link rel='stylesheet' "
44                 "href='http://fonts.googleapis.com/css?family=Roboto+Condensed'>\n"
45                 "<link rel='stylesheet' href='http://fonts.googleapis.com/"
46                 "css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold' "
47                 "title='roboto'>\n"
48                 "<link href='default.css' rel='stylesheet' type='text/css'>\n"
49                 "<link href='fullscreen.css' rel='stylesheet' class='fullscreen' "
50                 "type='text/css'>\n"
51                 "<body class='gc-documentation develop reference'>\n\n";
52        *file << "<h1>" << title << "</h1>\n";
53    } else {
54        *file << "page.title=RenderScript " << title << "\n\n";
55        *file << "@jd:body\n\n";
56    }
57    *file << "<div class='renderscript'>\n";
58}
59
60static void writeFooter(GeneratedFile* file, bool forVerification) {
61    *file << "</div>\n";
62    if (forVerification) {
63        *file << "</body></html>\n";
64    }
65}
66
67// If prefix starts input, copy it to stream and remove it from input.
68static void skipPrefix(ostringstream* stream, string* input, const string& prefix) {
69    size_t size = prefix.size();
70    if (input->compare(0, size, prefix) != 0) {
71        return;
72    }
73    input->erase(0, size);
74    *stream << prefix;
75}
76
77// Merge b into a.  Returns true if successful
78static bool mergeVersionInfo(VersionInfo* a, const VersionInfo& b) {
79    if (a->intSize != b.intSize) {
80        cerr << "Error.  We don't currently support versions that differ based on int size\n";
81        return false;
82    }
83    if (b.minVersion != 0 && a->maxVersion == b.minVersion - 1) {
84        a->maxVersion = b.maxVersion;
85    } else if (b.maxVersion != 0 && a->minVersion == b.maxVersion + 1) {
86        a->minVersion = b.minVersion;
87    } else {
88        cerr << "Error.  This code currently assume that all versions are contiguous.  Don't know "
89                "how to merge versions (" << a->minVersion << " - " << a->maxVersion << ") and ("
90             << b.minVersion << " - " << b.maxVersion << ")\n";
91        return false;
92    }
93    return true;
94}
95
96static string getHtmlStringForType(const ParameterDefinition& parameter) {
97    string s = parameter.rsType;
98    ostringstream stream;
99    skipPrefix(&stream, &s, "const ");
100    skipPrefix(&stream, &s, "volatile ");
101    bool endsWithAsterisk = s.size() > 0 && s[s.size() - 1] == '*';
102    if (endsWithAsterisk) {
103        s.erase(s.size() - 1, 1);
104    }
105
106    string anchor = systemSpecification.getHtmlAnchor(s);
107    if (anchor.empty()) {
108        // Not a RenderScript specific type.
109        return parameter.rsType;
110    } else {
111        stream << anchor;
112    }
113    if (endsWithAsterisk) {
114        stream << "*";
115    }
116    return stream.str();
117}
118
119static string getDetailedHtmlDeclaration(const FunctionPermutation& permutation) {
120    ostringstream stream;
121    auto ret = permutation.getReturn();
122    if (ret) {
123        stream << getHtmlStringForType(*ret);
124    } else {
125        stream << "void";
126    }
127    stream << " " << permutation.getName() << "(";
128    bool needComma = false;
129    for (auto p : permutation.getParams()) {
130        if (needComma) {
131            stream << ", ";
132        }
133        stream << getHtmlStringForType(*p);
134        if (p->isOutParameter) {
135            stream << "*";
136        }
137        if (!p->specName.empty()) {
138            stream << " " << p->specName;
139        }
140        needComma = true;
141    }
142    stream << ");\n";
143    return stream.str();
144}
145
146/* Some functions (like max) have changed implementations but not their
147 * declaration.  We need to unify these so that we don't end up with entries
148 * like:
149 *   char max(char a, char b);  Removed from API level 20
150 *   char max(char a, char b);  Added to API level 20
151 */
152static bool getUnifiedFunctionPrototypes(Function* function,
153                                         map<string, DetailedFunctionEntry>* entries) {
154    for (auto f : function->getSpecifications()) {
155        DetailedFunctionEntry entry;
156        entry.info = f->getVersionInfo();
157        for (auto p : f->getPermutations()) {
158            entry.htmlDeclaration = getDetailedHtmlDeclaration(*p);
159            const string s = stripHtml(entry.htmlDeclaration);
160            auto i = entries->find(s);
161            if (i == entries->end()) {
162                entries->insert(pair<string, DetailedFunctionEntry>(s, entry));
163            } else {
164                if (!mergeVersionInfo(&i->second.info, entry.info)) {
165                    return false;
166                }
167            }
168        }
169    }
170    return true;
171}
172
173// Convert words starting with @ into HTML references.  Returns false if error.
174static bool convertDocumentationRefences(string* s) {
175    bool success = true;
176    size_t end = 0;
177    for (;;) {
178        size_t start = s->find('@', end);
179        if (start == string::npos) {
180            break;
181        }
182        // Find the end of the identifier
183        end = start;
184        char c;
185        do {
186            c = (*s)[++end];
187        } while (isalnum(c) || c == '_');
188
189        const string id = s->substr(start + 1, end - start - 1);
190        string anchor = systemSpecification.getHtmlAnchor(id);
191        if (anchor.empty()) {
192            cerr << "Error:  Can't convert the documentation reference @" << id << "\n";
193            success = false;
194        }
195        s->replace(start, end - start, anchor);
196    }
197    return success;
198}
199
200static bool generateHtmlParagraphs(GeneratedFile* file, const vector<string>& description) {
201    bool inParagraph = false;
202    for (auto s : description) {
203        // Empty lines in the .spec marks paragraphs.
204        if (s.empty()) {
205            if (inParagraph) {
206                *file << "</p>\n";
207                inParagraph = false;
208            }
209        } else {
210            if (!inParagraph) {
211                *file << "<p> ";
212                inParagraph = true;
213            }
214        }
215        if (!convertDocumentationRefences(&s)) {
216            return false;
217        }
218        *file << s << "\n";
219    }
220    if (inParagraph) {
221        *file << "</p>\n";
222    }
223    return true;
224}
225
226static void writeSummaryTableStart(GeneratedFile* file, const string& label, bool labelIsHeading) {
227    if (labelIsHeading) {
228        *file << "<h2 style='margin-bottom: 0px;'>" << label << "</h2>\n";
229    }
230    *file << "<table class='jd-sumtable'><tbody>\n";
231    if (!labelIsHeading) {
232        *file << "  <tr><th colspan='2'>" << label << "</th></tr>\n";
233    }
234}
235
236static void writeSummaryTableEnd(GeneratedFile* file) {
237    *file << "</tbody></table>\n";
238}
239
240enum DeprecatedSelector {
241    DEPRECATED_ONLY,
242    NON_DEPRECATED_ONLY,
243    ALL,
244};
245
246static void writeSummaryTableEntry(ostream* stream, Definition* definition,
247                                   DeprecatedSelector deprecatedSelector) {
248    if (definition->hidden()) {
249        return;
250    }
251    const bool deprecated = definition->deprecated();
252    if ((deprecatedSelector == DEPRECATED_ONLY && !deprecated) ||
253        (deprecatedSelector == NON_DEPRECATED_ONLY && deprecated)) {
254        return;
255    }
256
257    *stream << "  <tr class='alt-color api apilevel-1'>\n";
258    *stream << "    <td class='jd-linkcol'>\n";
259    *stream << "      <a href='" << definition->getUrl() << "'>" << definition->getName()
260            << "</a>\n";
261    *stream << "    </td>\n";
262    *stream << "    <td class='jd-descrcol' width='100%'>\n";
263    *stream << "      ";
264    if (deprecated) {
265        *stream << "<b>Deprecated</b>.  ";
266    }
267    *stream << definition->getSummary() << "\n";
268    *stream << "    </td>\n";
269    *stream << "  </tr>\n";
270}
271
272static void writeSummaryTable(GeneratedFile* file, const ostringstream* entries, const char* name,
273                              DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
274    string s = entries->str();
275    if (!s.empty()) {
276        string prefix;
277        if (deprecatedSelector == DEPRECATED_ONLY) {
278            prefix = "Deprecated ";
279        }
280        writeSummaryTableStart(file, prefix + name, labelAsHeader);
281        *file << s;
282        writeSummaryTableEnd(file);
283    }
284}
285
286static void writeSummaryTables(GeneratedFile* file, const map<string, Constant*>& constants,
287                               const map<string, Type*>& types,
288                               const map<string, Function*>& functions,
289                               DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
290    ostringstream constantStream;
291    for (auto e : constants) {
292        writeSummaryTableEntry(&constantStream, e.second, deprecatedSelector);
293    }
294    writeSummaryTable(file, &constantStream, "Constants", deprecatedSelector, labelAsHeader);
295
296    ostringstream typeStream;
297    for (auto e : types) {
298        writeSummaryTableEntry(&typeStream, e.second, deprecatedSelector);
299    }
300    writeSummaryTable(file, &typeStream, "Types", deprecatedSelector, labelAsHeader);
301
302    ostringstream functionStream;
303    for (auto e : functions) {
304        writeSummaryTableEntry(&functionStream, e.second, deprecatedSelector);
305    }
306    writeSummaryTable(file, &functionStream, "Functions", deprecatedSelector, labelAsHeader);
307}
308
309static void writeHtmlVersionTag(GeneratedFile* file, VersionInfo info,
310                                bool addSpacing) {
311    ostringstream stream;
312    if (info.intSize == 32) {
313        stream << "When compiling for 32 bits. ";
314    } else if (info.intSize == 64) {
315        stream << "When compiling for 64 bits. ";
316    }
317
318    if (info.minVersion > 1 || info.maxVersion) {
319        const char* mid =
320                    "<a "
321                    "href='http://developer.android.com/guide/topics/manifest/"
322                    "uses-sdk-element.html#ApiLevels'>API level ";
323        if (info.minVersion <= 1) {
324            // No minimum
325            if (info.maxVersion > 0) {
326                stream << "Removed from " << mid << info.maxVersion + 1 << " and higher";
327            }
328        } else {
329            if (info.maxVersion == 0) {
330                // No maximum
331                stream << "Added in " << mid << info.minVersion;
332            } else {
333                stream << mid << info.minVersion << " - " << info.maxVersion;
334            }
335        }
336        stream << "</a>";
337    }
338    string s = stream.str();
339    // Remove any trailing whitespace
340    while (s.back() == ' ') {
341        s.pop_back();
342    }
343    if (!s.empty()) {
344        *file << (addSpacing ? "    " : "") << s << "\n";
345    }
346}
347
348static void writeDetailedTypeSpecification(GeneratedFile* file, const TypeSpecification* spec) {
349    switch (spec->getKind()) {
350        case SIMPLE: {
351            Type* type = spec->getType();
352            *file << "<p>A typedef of: " << spec->getSimpleType()
353                  << makeAttributeTag(spec->getAttribute(), "", type->getDeprecatedApiLevel(),
354                                      type->getDeprecatedMessage())
355                  << "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
356            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
357            *file << "</p>\n";
358            break;
359        }
360        case RS_OBJECT: {
361            *file << "<p>";
362            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
363            *file << "</p>\n";
364            break;
365        }
366        case ENUM: {
367            *file << "<p>An enum with the following values:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n";
368            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
369            *file << "</p>\n";
370
371            *file << "  <table class='jd-tagtable'><tbody>\n";
372            const vector<string>& values = spec->getValues();
373            const vector<string>& valueComments = spec->getValueComments();
374            for (size_t i = 0; i < values.size(); i++) {
375                *file << "    <tr><th>" << values[i] << "</th><td>";
376                if (valueComments.size() > i) {
377                    *file << valueComments[i];
378                }
379                *file << "</td></tr>\n";
380            }
381            *file << "  </tbody></table><br/>\n";
382            break;
383        }
384        case STRUCT: {
385            *file << "<p>A structure with the following fields:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
386            writeHtmlVersionTag(file, spec->getVersionInfo(), false);
387            *file << "</p>\n";
388
389            *file << "  <table class='jd-tagtable'><tbody>\n";
390            const vector<string>& fields = spec->getFields();
391            const vector<string>& fieldComments = spec->getFieldComments();
392            for (size_t i = 0; i < fields.size(); i++) {
393                *file << "    <tr><th>" << fields[i] << "</th><td>";
394                if (fieldComments.size() > i && !fieldComments[i].empty()) {
395                    *file << fieldComments[i];
396                }
397                *file << "</td></tr>\n";
398            }
399            *file << "  </tbody></table><br/>\n";
400            break;
401        }
402    }
403}
404
405static void writeDetailedConstantSpecification(GeneratedFile* file, ConstantSpecification* c) {
406    *file << "      <tr><td>";
407    *file << "Value: " << c->getValue() << "\n";
408    writeHtmlVersionTag(file, c->getVersionInfo(), true);
409    *file << "      </td></tr>\n";
410    *file << "<br/>\n";
411}
412
413static bool writeOverviewForFile(GeneratedFile* file, const SpecFile& specFile) {
414    bool success = true;
415    *file << "<h2>" << specFile.getBriefDescription() << "</h2>\n";
416    if (!generateHtmlParagraphs(file, specFile.getFullDescription())) {
417        success = false;
418    }
419
420    // Write the summary tables.
421    // file << "<h2>Summary</h2>\n";
422    writeSummaryTables(file, specFile.getDocumentedConstants(), specFile.getDocumentedTypes(),
423                       specFile.getDocumentedFunctions(), NON_DEPRECATED_ONLY, false);
424
425    return success;
426}
427
428static bool generateOverview(const string& directory, bool forVerification) {
429    GeneratedFile file;
430    if (!file.start(directory, forVerification ? OVERVIEW_HTML_FILE_NAME : OVERVIEW_JD_FILE_NAME)) {
431        return false;
432    }
433    bool success = true;
434
435    writeHeader(&file, forVerification, "Runtime API Reference");
436
437    for (auto specFile : systemSpecification.getSpecFiles()) {
438        if (!writeOverviewForFile(&file, *specFile)) {
439            success = false;
440        }
441    }
442
443    writeFooter(&file, forVerification);
444    file.close();
445    return success;
446}
447
448static bool generateAlphabeticalIndex(const string& directory, bool forVerification) {
449    GeneratedFile file;
450    if (!file.start(directory, forVerification ? INDEX_HTML_FILE_NAME : INDEX_JD_FILE_NAME)) {
451        return false;
452    }
453    writeHeader(&file, forVerification, "Index");
454
455    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
456                       systemSpecification.getFunctions(), NON_DEPRECATED_ONLY, true);
457
458    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
459                       systemSpecification.getFunctions(), DEPRECATED_ONLY, true);
460
461    writeFooter(&file, forVerification);
462    file.close();
463    return true;
464}
465
466static void writeDeprecatedWarning(GeneratedFile* file, Definition* definition) {
467    if (definition->deprecated()) {
468        *file << "    <p><b>Deprecated.</b>  ";
469        string s = definition->getDeprecatedMessage();
470        convertDocumentationRefences(&s);
471        if (!s.empty()) {
472            *file << s;
473        } else {
474            *file << "Do not use.";
475        }
476        *file << "</p>\n";
477    }
478}
479
480static bool writeDetailedConstant(GeneratedFile* file, Constant* constant) {
481    if (constant->hidden()) {
482        return true;
483    }
484    const string& name = constant->getName();
485
486    *file << "<a name='android_rs:" << name << "'></a>\n";
487    *file << "<div class='jd-details'>\n";
488    *file << "  <h4 class='jd-details-title'>\n";
489    *file << "    <span class='sympad'>" << name << "</span>\n";
490    *file << "    <span class='normal'>: " << constant->getSummary() << "</span>\n";
491    *file << "  </h4>\n";
492
493    *file << "  <div class='jd-details-descr'>\n";
494    *file << "    <table class='jd-tagtable'><tbody>\n";
495    auto specifications = constant->getSpecifications();
496    bool addSeparator = specifications.size() > 1;
497    for (auto spec : specifications) {
498        if (addSeparator) {
499            *file << "    <h5 class='jd-tagtitle'>Variant:</h5>\n";
500        }
501        writeDetailedConstantSpecification(file, spec);
502    }
503    *file << "    </tbody></table>\n";
504    *file << "  </div>\n";
505
506    *file << "    <div class='jd-tagdata jd-tagdescr'>\n";
507
508    writeDeprecatedWarning(file, constant);
509    if (!generateHtmlParagraphs(file, constant->getDescription())) {
510        return false;
511    }
512    *file << "    </div>\n";
513
514    *file << "</div>\n";
515    *file << "\n";
516    return true;
517}
518
519static bool writeDetailedType(GeneratedFile* file, Type* type) {
520    if (type->hidden()) {
521        return true;
522    }
523    const string& name = type->getName();
524
525    *file << "<a name='android_rs:" << name << "'></a>\n";
526    *file << "<div class='jd-details'>\n";
527    *file << "  <h4 class='jd-details-title'>\n";
528    *file << "    <span class='sympad'>" << name << "</span>\n";
529    *file << "    <span class='normal'>: " << type->getSummary() << "</span>\n";
530    *file << "  </h4>\n";
531
532    *file << "  <div class='jd-details-descr'>\n";
533    for (auto spec : type->getSpecifications()) {
534        writeDetailedTypeSpecification(file, spec);
535    }
536
537    writeDeprecatedWarning(file, type);
538    if (!generateHtmlParagraphs(file, type->getDescription())) {
539        return false;
540    }
541
542    *file << "  </div>\n";
543    *file << "</div>\n";
544    *file << "\n";
545    return true;
546}
547
548static bool writeDetailedFunction(GeneratedFile* file, Function* function) {
549    if (function->hidden()) {
550        return true;
551    }
552    const string& name = function->getName();
553
554    *file << "<a name='android_rs:" << name << "'></a>\n";
555    *file << "<div class='jd-details'>\n";
556    *file << "  <h4 class='jd-details-title'>\n";
557    *file << "    <span class='sympad'>" << name << "</span>\n";
558    *file << "    <span class='normal'>: " << function->getSummary() << "</span>\n";
559    *file << "  </h4>\n";
560
561    *file << "  <div class='jd-details-descr'>\n";
562    map<string, DetailedFunctionEntry> entries;
563    if (!getUnifiedFunctionPrototypes(function, &entries)) {
564        return false;
565    }
566    *file << "    <table class='jd-tagtable'><tbody>\n";
567    for (auto i : entries) {
568        *file << "      <tr>\n";
569        *file << "        <td>" << i.second.htmlDeclaration << "</td>\n";
570        *file << "        <td>";
571        writeHtmlVersionTag(file, i.second.info, true);
572        *file << "        </td>\n";
573        *file << "      </tr>\n";
574    }
575    *file << "    </tbody></table>\n";
576    *file << "  </div>\n";
577
578    if (function->someParametersAreDocumented()) {
579        *file << "  <div class='jd-tagdata'>";
580        *file << "    <h5 class='jd-tagtitle'>Parameters</h5>\n";
581        *file << "    <table class='jd-tagtable'><tbody>\n";
582        for (ParameterEntry* p : function->getParameters()) {
583            *file << "    <tr><th>" << p->name << "</th><td>" << p->documentation << "</td></tr>\n";
584        }
585        *file << "    </tbody></table>\n";
586        *file << "  </div>\n";
587    }
588
589    string ret = function->getReturnDocumentation();
590    if (!ret.empty()) {
591        *file << "  <div class='jd-tagdata'>";
592        *file << "    <h5 class='jd-tagtitle'>Returns</h5>\n";
593        *file << "    <table class='jd-tagtable'><tbody>\n";
594        *file << "    <tr><td>" << ret << "</td></tr>\n";
595        *file << "    </tbody></table>\n";
596        *file << "  </div>\n";
597    }
598
599    *file << "  <div class='jd-tagdata jd-tagdescr'>\n";
600    writeDeprecatedWarning(file, function);
601    if (!generateHtmlParagraphs(file, function->getDescription())) {
602        return false;
603    }
604    *file << "  </div>\n";
605
606    *file << "</div>\n";
607    *file << "\n";
608    return true;
609}
610
611static bool writeDetailedDocumentationFile(const string& directory, const SpecFile& specFile,
612                                           bool forVerification) {
613    if (!specFile.hasSpecifications()) {
614        // This is true for rs_core.spec
615        return true;
616    }
617
618    GeneratedFile file;
619    const string fileName = stringReplace(specFile.getSpecFileName(), ".spec",
620                                          forVerification ? ".html" : ".jd");
621    if (!file.start(directory, fileName)) {
622        return false;
623    }
624    bool success = true;
625
626    string title = specFile.getBriefDescription();
627    writeHeader(&file, forVerification, title);
628
629    file << "<h2>Overview</h2>\n";
630    if (!generateHtmlParagraphs(&file, specFile.getFullDescription())) {
631        success = false;
632    }
633
634    // Write the summary tables.
635    file << "<h2>Summary</h2>\n";
636    const auto& constants = specFile.getDocumentedConstants();
637    const auto& types = specFile.getDocumentedTypes();
638    const auto& functions = specFile.getDocumentedFunctions();
639
640    writeSummaryTables(&file, constants, types, functions, NON_DEPRECATED_ONLY, false);
641    writeSummaryTables(&file, constants, types, functions, DEPRECATED_ONLY, false);
642
643    // Write the full details of each constant, type, and function.
644    if (!constants.empty()) {
645        file << "<h2>Constants</h2>\n";
646        for (auto i : constants) {
647            if (!writeDetailedConstant(&file, i.second)) {
648                success = false;
649            }
650        }
651    }
652    if (!types.empty()) {
653        file << "<h2>Types</h2>\n";
654        for (auto i : types) {
655            if (!writeDetailedType(&file, i.second)) {
656                success = false;
657            }
658        }
659    }
660    if (!functions.empty()) {
661        file << "<h2>Functions</h2>\n";
662        for (auto i : functions) {
663            if (!writeDetailedFunction(&file, i.second)) {
664                success = false;
665            }
666        }
667    }
668
669    writeFooter(&file, forVerification);
670    file.close();
671
672    if (!success) {
673        // If in error, write a final message to make it easier to figure out which file failed.
674        cerr << fileName << ": Failed due to errors.\n";
675    }
676    return success;
677}
678
679static void generateSnippet(GeneratedFile* file, const string& fileName, const string& title) {
680    const char offset[] = "                  ";
681    *file << offset << "<li><a href=\"<?cs var:toroot ?>guide/topics/renderscript/reference/"
682          << fileName << "\">\n";
683    *file << offset << "  <span class=\"en\">" << title << "</span>\n";
684    *file << offset << "</a></li>\n";
685}
686
687/* Generate a partial file of links that should be cut & pasted into the proper section of the
688 * guide_toc.cs file.
689 */
690static bool generateAndroidTableOfContentSnippet(const string& directory) {
691    GeneratedFile file;
692    if (!file.start(directory, "guide_toc.cs")) {
693        return false;
694    }
695    file << "<!-- Copy and paste the following lines into the RenderScript section of\n";
696    file << "     platform/frameworks/base/docs/html/guide/guide_toc.cs\n\n";
697
698    const char offset[] = "              ";
699    file << offset << "<li class=\"nav-section\">\n";
700    file << offset << "  <div class=\"nav-section-header\">\n";
701    file << offset << "    <a href=\"<?cs var:toroot ?>guide/topics/renderscript/reference/" <<
702            OVERVIEW_HTML_FILE_NAME << "\">\n";
703    file << offset << "      <span class=\"en\">Runtime API Reference</span>\n";
704    file << offset << "    </a></div>\n";
705    file << offset << "  <ul>\n";
706
707    for (auto specFile : systemSpecification.getSpecFiles()) {
708        if (specFile->hasSpecifications()) {
709            const string fileName = stringReplace(specFile->getSpecFileName(), ".spec", ".html");
710            generateSnippet(&file, fileName, specFile->getBriefDescription());
711        }
712    }
713    generateSnippet(&file, INDEX_HTML_FILE_NAME, "Index");
714
715    file << offset << "  </ul>\n";
716    file << offset << "</li>\n";
717
718    return true;
719}
720
721bool generateDocumentation(const string& directory, bool forVerification) {
722    bool success = generateOverview(directory, forVerification) &&
723                   generateAlphabeticalIndex(directory, forVerification) &&
724                   generateAndroidTableOfContentSnippet(directory);
725    for (auto specFile : systemSpecification.getSpecFiles()) {
726        if (!writeDetailedDocumentationFile(directory, *specFile, forVerification)) {
727            success = false;
728        }
729    }
730    return success;
731}
732