Update documentation generator to work with the Documentation system.

Also added this flag to the generator:
-H  Now that we generate by default .jd files rather than .html files,
    you can use this flag to revert to generating .html files.  This is
    useful when verifying doc changes locally.

And modified the -v flag to specify the API level for all file generation
rather than just the testing files.

Change-Id: Ic9e35ad6779b9fbc6b23228dded2e2be864393ff
diff --git a/api/GenerateDocumentation.cpp b/api/GenerateDocumentation.cpp
new file mode 100644
index 0000000..eab1889
--- /dev/null
+++ b/api/GenerateDocumentation.cpp
@@ -0,0 +1,736 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <sstream>
+
+#include "Generator.h"
+#include "Specification.h"
+#include "Utilities.h"
+
+using namespace std;
+
+struct DetailedFunctionEntry {
+    VersionInfo info;
+    string htmlDeclaration;
+};
+
+static const char OVERVIEW_HTML_FILE_NAME[] = "overview.html";
+static const char OVERVIEW_JD_FILE_NAME[] = "overview.jd";
+static const char INDEX_HTML_FILE_NAME[] = "index.html";
+static const char INDEX_JD_FILE_NAME[] = "index.jd";
+
+static void writeHeader(GeneratedFile* file, bool forVerification, const string& title) {
+    if (forVerification) {
+        *file << "<!DOCTYPE html>\n";
+        *file << "<!-- " << AUTO_GENERATED_WARNING << "-->\n";
+
+        *file << "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>\n"
+                 "<meta name='viewport' content='width=device-width'>\n"
+                 "<link rel='shortcut icon' type='image/x-icon' "
+                 "href='http://developer.android.com/favicon.ico'>\n"
+                 "<title>android.renderscript | Android Developers</title>\n"
+                 "<!-- STYLESHEETS -->\n"
+                 "<link rel='stylesheet' "
+                 "href='http://fonts.googleapis.com/css?family=Roboto+Condensed'>\n"
+                 "<link rel='stylesheet' href='http://fonts.googleapis.com/"
+                 "css?family=Roboto:light,regular,medium,thin,italic,mediumitalic,bold' "
+                 "title='roboto'>\n"
+                 "<link href='./test_files/default.css' rel='stylesheet' type='text/css'>\n"
+                 "<!-- FULLSCREEN STYLESHEET -->\n"
+                 "<link href='./test_files/fullscreen.css' rel='stylesheet' class='fullscreen' "
+                 "type='text/css'>\n"
+                 "<!-- JAVASCRIPT -->\n"
+                 "<script src='./test_files/cb=gapi.loaded_0' async=''></script><script "
+                 "type='text/javascript' async='' src='./test_files/plusone.js' "
+                 "gapi_processed='true'></script><script async='' "
+                 "src='./test_files/analytics.js'></script><script src='./test_files/jsapi' "
+                 "type='text/javascript'></script>\n"
+                 "<script src='./test_files/android_3p-bundle.js' "
+                 "type='text/javascript'></script>\n"
+                 "<script type='text/javascript'>\n"
+                 "  var toRoot = '/';\n"
+                 "  var metaTags = [];\n"
+                 "  var devsite = false;\n"
+                 "</script>\n"
+                 "<script src='./test_files/docs.js' type='text/javascript'></script><script "
+                 "type='text/javascript' src='./test_files/saved_resource'></script>\n"
+                 "<script>\n"
+                 "  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n"
+                 "  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new "
+                 "Date();a=s.createElement(o),\n"
+                 "  "
+                 "m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n"
+                 "  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n"
+                 "  ga('create', 'UA-5831155-1', 'android.com');\n"
+                 "  ga('create', 'UA-49880327-2', 'android.com', {'name': 'universal'});  // New "
+                 "tracker);\n"
+                 "  ga('send', 'pageview');\n"
+                 "  ga('universal.send', 'pageview'); // Send page view for new tracker.\n"
+                 "</script>\n"
+                 "<link type='text/css' href='./test_files/default+en.css' "
+                 "rel='stylesheet'><script "
+                 "type='text/javascript' src='./test_files/default+en.I.js'></script></head>\n"
+                 "<body class='gc-documentation\n"
+                 "  develop reference'>\n\n";
+        *file << "<h1>" << title << "</h1>\n";
+    } else {
+        *file << "page.title=RenderScript " << title << "\n\n";
+        *file << "@jd:body\n\n";
+    }
+}
+
+static void writeFooter(GeneratedFile* file, bool forVerification) {
+    if (forVerification) {
+        *file << "<!-- end body-content -->\n</body></html>\n";
+    }
+}
+
+// If prefix starts input, copy it to stream and remove it from input.
+static void skipPrefix(ostringstream* stream, string* input, const string& prefix) {
+    size_t size = prefix.size();
+    if (input->compare(0, size, prefix) != 0) {
+        return;
+    }
+    input->erase(0, size);
+    *stream << prefix;
+}
+
+// Merge b into a.  Returns true if successful
+static bool mergeVersionInfo(VersionInfo* a, const VersionInfo& b) {
+    if (a->intSize != b.intSize) {
+        cerr << "Error.  We don't currently support versions that differ based on int size\n";
+        return false;
+    }
+    if (b.minVersion != 0 && a->maxVersion == b.minVersion - 1) {
+        a->maxVersion = b.maxVersion;
+    } else if (b.maxVersion != 0 && a->minVersion == b.maxVersion + 1) {
+        a->minVersion = b.minVersion;
+    } else {
+        cerr << "Error.  This code currently assume that all versions are contiguous.  Don't know "
+                "how to merge versions (" << a->minVersion << " - " << a->maxVersion << ") and ("
+             << b.minVersion << " - " << b.maxVersion << ")\n";
+        return false;
+    }
+    return true;
+}
+
+static string getHtmlStringForType(const ParameterDefinition& parameter) {
+    string s = parameter.rsType;
+    ostringstream stream;
+    skipPrefix(&stream, &s, "const ");
+    skipPrefix(&stream, &s, "volatile ");
+    bool endsWithAsterisk = s.size() > 0 && s[s.size() - 1] == '*';
+    if (endsWithAsterisk) {
+        s.erase(s.size() - 1, 1);
+    }
+
+    string anchor = systemSpecification.getHtmlAnchor(s);
+    if (anchor.empty()) {
+        // Not a RenderScript specific type.
+        return parameter.rsType;
+    } else {
+        stream << anchor;
+    }
+    if (endsWithAsterisk) {
+        stream << "*";
+    }
+    return stream.str();
+}
+
+static string getDetailedHtmlDeclaration(const FunctionPermutation& permutation) {
+    ostringstream stream;
+    auto ret = permutation.getReturn();
+    if (ret) {
+        stream << getHtmlStringForType(*ret);
+    } else {
+        stream << "void";
+    }
+    stream << " " << permutation.getName() << "(";
+    bool needComma = false;
+    for (auto p : permutation.getParams()) {
+        if (needComma) {
+            stream << ", ";
+        }
+        stream << getHtmlStringForType(*p);
+        if (p->isOutParameter) {
+            stream << "*";
+        }
+        if (!p->specName.empty()) {
+            stream << " " << p->specName;
+        }
+        needComma = true;
+    }
+    stream << ");\n";
+    return stream.str();
+}
+
+/* Some functions (like max) have changed implementations but not their
+ * declaration.  We need to unify these so that we don't end up with entries
+ * like:
+ *   char max(char a, char b);  Removed from API level 20
+ *   char max(char a, char b);  Added to API level 20
+ */
+static bool getUnifiedFunctionPrototypes(Function* function,
+                                         map<string, DetailedFunctionEntry>* entries) {
+    for (auto f : function->getSpecifications()) {
+        DetailedFunctionEntry entry;
+        entry.info = f->getVersionInfo();
+        for (auto p : f->getPermutations()) {
+            entry.htmlDeclaration = getDetailedHtmlDeclaration(*p);
+            const string s = stripHtml(entry.htmlDeclaration);
+            auto i = entries->find(s);
+            if (i == entries->end()) {
+                entries->insert(pair<string, DetailedFunctionEntry>(s, entry));
+            } else {
+                if (!mergeVersionInfo(&i->second.info, entry.info)) {
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+// Convert words starting with @ into HTML references.  Returns false if error.
+static bool convertDocumentationRefences(string* s) {
+    bool success = true;
+    size_t end = 0;
+    for (;;) {
+        size_t start = s->find('@', end);
+        if (start == string::npos) {
+            break;
+        }
+        // Find the end of the identifier
+        end = start;
+        char c;
+        do {
+            c = (*s)[++end];
+        } while (isalnum(c) || c == '_');
+
+        const string id = s->substr(start + 1, end - start - 1);
+        string anchor = systemSpecification.getHtmlAnchor(id);
+        if (anchor.empty()) {
+            cerr << "Error:  Can't convert the documentation reference @" << id << "\n";
+            success = false;
+        }
+        s->replace(start, end - start, anchor);
+    }
+    return success;
+}
+
+static bool generateHtmlParagraphs(GeneratedFile* file, const vector<string>& description) {
+    bool inParagraph = false;
+    for (auto s : description) {
+        // Empty lines in the .spec marks paragraphs.
+        if (s.empty()) {
+            if (inParagraph) {
+                *file << "</p>\n";
+                inParagraph = false;
+            }
+        } else {
+            if (!inParagraph) {
+                *file << "<p> ";
+                inParagraph = true;
+            }
+        }
+        if (!convertDocumentationRefences(&s)) {
+            return false;
+        }
+        *file << s << "\n";
+    }
+    if (inParagraph) {
+        *file << "</p>\n";
+    }
+    return true;
+}
+
+static void writeSummaryTableStart(GeneratedFile* file, const string& label, bool labelIsHeading) {
+    if (labelIsHeading) {
+        *file << "<h2 style='margin-bottom: 0px;'>" << label << "</h2><hr/>\n";
+    }
+    *file << "<table class='jd-sumtable'><tbody>\n";
+    if (!labelIsHeading) {
+        *file << "  <tr><th colspan='2'>" << label << "</th></tr>\n";
+    }
+}
+
+static void writeSummaryTableEnd(GeneratedFile* file) {
+    *file << "</tbody></table>\n";
+}
+
+enum DeprecatedSelector {
+    DEPRECATED_ONLY,
+    NON_DEPRECATED_ONLY,
+    ALL,
+};
+
+static void writeSummaryTableEntry(ostream* stream, Definition* definition,
+                                   DeprecatedSelector deprecatedSelector) {
+    if (definition->hidden()) {
+        return;
+    }
+    const bool deprecated = definition->deprecated();
+    if ((deprecatedSelector == DEPRECATED_ONLY && !deprecated) ||
+        (deprecatedSelector == NON_DEPRECATED_ONLY && deprecated)) {
+        return;
+    }
+
+    *stream << "  <tr class='alt-color api apilevel-1'>\n";
+    *stream << "    <td class='jd-linkcol'>\n";
+    *stream << "      <a href='" << definition->getUrl() << "'>" << definition->getName()
+            << "</a>\n";
+    *stream << "    </td>\n";
+    *stream << "    <td class='jd-descrcol' width='100%'>\n";
+    *stream << "      ";
+    if (deprecated) {
+        *stream << "<b>Deprecated</b>.  ";
+    }
+    *stream << definition->getSummary() << "\n";
+    *stream << "    </td>\n";
+    *stream << "  </tr>\n";
+}
+
+static void writeSummaryTable(GeneratedFile* file, const ostringstream* entries, const char* name,
+                              DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
+    string s = entries->str();
+    if (!s.empty()) {
+        string prefix;
+        if (deprecatedSelector == DEPRECATED_ONLY) {
+            prefix = "Deprecated ";
+        }
+        writeSummaryTableStart(file, prefix + name, labelAsHeader);
+        *file << s;
+        writeSummaryTableEnd(file);
+    }
+}
+
+static void writeSummaryTables(GeneratedFile* file, const map<string, Constant*>& constants,
+                               const map<string, Type*>& types,
+                               const map<string, Function*>& functions,
+                               DeprecatedSelector deprecatedSelector, bool labelAsHeader) {
+    ostringstream constantStream;
+    for (auto e : constants) {
+        writeSummaryTableEntry(&constantStream, e.second, deprecatedSelector);
+    }
+    writeSummaryTable(file, &constantStream, "Constants", deprecatedSelector, labelAsHeader);
+
+    ostringstream typeStream;
+    for (auto e : types) {
+        writeSummaryTableEntry(&typeStream, e.second, deprecatedSelector);
+    }
+    writeSummaryTable(file, &typeStream, "Types", deprecatedSelector, labelAsHeader);
+
+    ostringstream functionStream;
+    for (auto e : functions) {
+        writeSummaryTableEntry(&functionStream, e.second, deprecatedSelector);
+    }
+    writeSummaryTable(file, &functionStream, "Functions", deprecatedSelector, labelAsHeader);
+}
+
+static void writeHtmlVersionTag(GeneratedFile* file, VersionInfo info) {
+    ostringstream stream;
+    if (info.intSize == 32) {
+        stream << "When compiling for 32 bits. ";
+    } else if (info.intSize == 64) {
+        stream << "When compiling for 64 bits. ";
+    }
+
+    if (info.minVersion > 1 || info.maxVersion) {
+        const char* mid =
+                    "<a "
+                    "href='http://developer.android.com/guide/topics/manifest/"
+                    "uses-sdk-element.html#ApiLevels'>API level ";
+        if (info.minVersion <= 1) {
+            // No minimum
+            if (info.maxVersion > 0) {
+                stream << "Removed from " << mid << info.maxVersion + 1;
+            }
+        } else {
+            if (info.maxVersion == 0) {
+                // No maximum
+                stream << "Added in " << mid << info.minVersion;
+            } else {
+                stream << mid << info.minVersion << " - " << info.maxVersion;
+            }
+        }
+        stream << "</a>";
+    }
+    const string s = stream.str();
+    if (!s.empty()) {
+        *file << "    " << s << "\n";
+    }
+}
+
+static void writeDetailedTypeSpecification(GeneratedFile* file, const TypeSpecification* type) {
+    switch (type->getKind()) {
+        case SIMPLE:
+            *file << "<p>A typedef of: " << type->getSimpleType()
+                  << "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+            writeHtmlVersionTag(file, type->getVersionInfo());
+            *file << "</p>\n";
+            break;
+        case ENUM: {
+            *file << "<p>An enum with the following values:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\n";
+            writeHtmlVersionTag(file, type->getVersionInfo());
+            *file << "</p>\n";
+
+            *file << "  <table class='jd-tagtable'><tbody>\n";
+            const vector<string>& values = type->getValues();
+            const vector<string>& valueComments = type->getValueComments();
+            for (size_t i = 0; i < values.size(); i++) {
+                *file << "    <tr><th>" << values[i] << "</th><td>";
+                if (valueComments.size() > i) {
+                    *file << valueComments[i];
+                }
+                *file << "</td></tr>\n";
+            }
+            *file << "  </tbody></table><br/>\n";
+            break;
+        }
+        case STRUCT: {
+            *file << "<p>A structure with the following fields:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+            writeHtmlVersionTag(file, type->getVersionInfo());
+            *file << "</p>\n";
+
+            *file << "  <table class='jd-tagtable'><tbody>\n";
+            const vector<string>& fields = type->getFields();
+            const vector<string>& fieldComments = type->getFieldComments();
+            for (size_t i = 0; i < fields.size(); i++) {
+                *file << "    <tr><th>" << fields[i] << "</th><td>";
+                if (fieldComments.size() > i && !fieldComments[i].empty()) {
+                    *file << fieldComments[i];
+                }
+                *file << "</td></tr>\n";
+            }
+            *file << "  </tbody></table><br/>\n";
+            break;
+        }
+    }
+}
+
+static void writeDetailedConstantSpecification(GeneratedFile* file, ConstantSpecification* c) {
+    *file << "      <tr><td>";
+    *file << "Value: " << c->getValue() << "\n";
+    writeHtmlVersionTag(file, c->getVersionInfo());
+    *file << "      </td></tr>\n";
+    *file << "<br/>\n";
+}
+
+static bool writeOverviewForFile(GeneratedFile* file, const SpecFile& specFile) {
+    bool success = true;
+    *file << "<h2>" << specFile.getBriefDescription() << "</h2>\n";
+    if (!generateHtmlParagraphs(file, specFile.getFullDescription())) {
+        success = false;
+    }
+
+    // Write the summary tables.
+    // file << "<h2>Summary</h2>\n";
+    writeSummaryTables(file, specFile.getDocumentedConstants(), specFile.getDocumentedTypes(),
+                       specFile.getDocumentedFunctions(), NON_DEPRECATED_ONLY, false);
+
+    return success;
+}
+
+static bool generateOverview(const string& directory, bool forVerification) {
+    GeneratedFile file;
+    if (!file.start(directory, forVerification ? OVERVIEW_HTML_FILE_NAME : OVERVIEW_JD_FILE_NAME)) {
+        return false;
+    }
+    bool success = true;
+
+    writeHeader(&file, forVerification, "Overview");
+
+    for (auto specFile : systemSpecification.getSpecFiles()) {
+        if (!writeOverviewForFile(&file, *specFile)) {
+            success = false;
+        }
+    }
+
+    writeFooter(&file, forVerification);
+    file.close();
+    return success;
+}
+
+static bool generateAlphabeticalIndex(const string& directory, bool forVerification) {
+    GeneratedFile file;
+    if (!file.start(directory, forVerification ? INDEX_HTML_FILE_NAME : INDEX_JD_FILE_NAME)) {
+        return false;
+    }
+    writeHeader(&file, forVerification, "Index");
+
+    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
+                       systemSpecification.getFunctions(), NON_DEPRECATED_ONLY, true);
+
+    writeSummaryTables(&file, systemSpecification.getConstants(), systemSpecification.getTypes(),
+                       systemSpecification.getFunctions(), DEPRECATED_ONLY, true);
+
+    writeFooter(&file, forVerification);
+    file.close();
+    return true;
+}
+
+static void writeDeprecatedWarning(GeneratedFile* file, Definition* definition) {
+    if (definition->deprecated()) {
+        *file << "    <p><b>Deprecated.</b>  ";
+        string s = definition->getDeprecatedMessage();
+        convertDocumentationRefences(&s);
+        if (!s.empty()) {
+            *file << s;
+        } else {
+            *file << "Do not use.";
+        }
+        *file << "</p>\n";
+    }
+}
+
+static bool writeDetailedConstant(GeneratedFile* file, Constant* constant) {
+    if (constant->hidden()) {
+        return true;
+    }
+    const string& name = constant->getName();
+
+    *file << "<a id='android_rs:" << name << "'></a>\n";
+    *file << "<div class='jd-details'>\n";
+    *file << "  <h4 class='jd-details-title'>\n";
+    *file << "    <span class='sympad'>" << name << "</span>\n";
+    *file << "    <span class='normal'>: " << constant->getSummary() << "</span>\n";
+    *file << "  </h4>\n";
+
+    *file << "  <div class='jd-details-descr'>\n";
+    *file << "    <table class='jd-tagtable'><tbody>\n";
+    auto specifications = constant->getSpecifications();
+    bool addSeparator = specifications.size() > 1;
+    for (auto spec : specifications) {
+        if (addSeparator) {
+            *file << "    <h5 class='jd-tagtitle'>Variant:</h5>\n";
+        }
+        writeDetailedConstantSpecification(file, spec);
+    }
+    *file << "    </tbody></table>\n";
+    *file << "  </div>\n";
+
+    *file << "    <div class='jd-tagdata jd-tagdescr'>\n";
+
+    writeDeprecatedWarning(file, constant);
+    if (!generateHtmlParagraphs(file, constant->getDescription())) {
+        return false;
+    }
+    *file << "    </div>\n";
+
+    *file << "</div>\n";
+    *file << "\n";
+    return true;
+}
+
+static bool writeDetailedType(GeneratedFile* file, Type* type) {
+    if (type->hidden()) {
+        return true;
+    }
+    const string& name = type->getName();
+
+    *file << "<a id='android_rs:" << name << "'></a>\n";
+    *file << "<div class='jd-details'>\n";
+    *file << "  <h4 class='jd-details-title'>\n";
+    *file << "    <span class='sympad'>" << name << "</span>\n";
+    *file << "    <span class='normal'>: " << type->getSummary() << "</span>\n";
+    *file << "  </h4>\n";
+
+    *file << "  <div class='jd-details-descr'>\n";
+    for (auto spec : type->getSpecifications()) {
+        writeDetailedTypeSpecification(file, spec);
+    }
+
+    writeDeprecatedWarning(file, type);
+    if (!generateHtmlParagraphs(file, type->getDescription())) {
+        return false;
+    }
+
+    *file << "  </div>\n";
+    *file << "</div>\n";
+    *file << "\n";
+    return true;
+}
+
+static bool writeDetailedFunction(GeneratedFile* file, Function* function) {
+    const string& name = function->getName();
+
+    *file << "<a id='android_rs:" << name << "'></a>\n";
+    *file << "<div class='jd-details'>\n";
+    *file << "  <h4 class='jd-details-title'>\n";
+    *file << "    <span class='sympad'>" << name << "</span>\n";
+    *file << "    <span class='normal'>: " << function->getSummary() << "</span>\n";
+    *file << "  </h4>\n";
+
+    *file << "  <div class='jd-details-descr'>\n";
+    map<string, DetailedFunctionEntry> entries;
+    if (!getUnifiedFunctionPrototypes(function, &entries)) {
+        return false;
+    }
+    *file << "    <table class='jd-tagtable'><tbody>\n";
+    for (auto i : entries) {
+        *file << "      <tr>\n";
+        *file << "        <td>" << i.second.htmlDeclaration << "</td>\n";
+        *file << "        <td>";
+        writeHtmlVersionTag(file, i.second.info);
+        *file << "        </td>\n";
+        *file << "      </tr>\n";
+    }
+    *file << "    </tbody></table>\n";
+    *file << "  </div>\n";
+
+    if (function->someParametersAreDocumented()) {
+        *file << "  <div class='jd-tagdata'>";
+        *file << "    <h5 class='jd-tagtitle'>Parameters</h5>\n";
+        *file << "    <table class='jd-tagtable'><tbody>\n";
+        for (ParameterEntry* p : function->getParameters()) {
+            *file << "    <tr><th>" << p->name << "</th><td>" << p->documentation << "</td></tr>\n";
+        }
+        *file << "    </tbody></table>\n";
+        *file << "  </div>\n";
+    }
+
+    string ret = function->getReturnDocumentation();
+    if (!ret.empty()) {
+        *file << "  <div class='jd-tagdata'>";
+        *file << "    <h5 class='jd-tagtitle'>Returns</h5>\n";
+        *file << "    <table class='jd-tagtable'><tbody>\n";
+        *file << "    <tr><td>" << ret << "</td></tr>\n";
+        *file << "    </tbody></table>\n";
+        *file << "  </div>\n";
+    }
+
+    *file << "  <div class='jd-tagdata jd-tagdescr'>\n";
+    writeDeprecatedWarning(file, function);
+    if (!generateHtmlParagraphs(file, function->getDescription())) {
+        return false;
+    }
+    *file << "  </div>\n";
+
+    *file << "</div>\n";
+    *file << "\n";
+    return true;
+}
+
+static bool writeDetailedDocumentationFile(const string& directory, const SpecFile& specFile,
+                                           bool forVerification) {
+    if (!specFile.hasSpecifications()) {
+        // This is true for rs_core.spec
+        return true;
+    }
+
+    GeneratedFile file;
+    const string fileName = stringReplace(specFile.getSpecFileName(), ".spec",
+                                          forVerification ? ".html" : ".jd");
+    if (!file.start(directory, fileName)) {
+        return false;
+    }
+    bool success = true;
+
+    string title = specFile.getBriefDescription();
+    writeHeader(&file, forVerification, title);
+
+    file << "<h2>Overview</h2>\n";
+    if (!generateHtmlParagraphs(&file, specFile.getFullDescription())) {
+        success = false;
+    }
+
+    // Write the summary tables.
+    file << "<h2>Summary</h2>\n";
+    const auto& constants = specFile.getDocumentedConstants();
+    const auto& types = specFile.getDocumentedTypes();
+    const auto& functions = specFile.getDocumentedFunctions();
+
+    writeSummaryTables(&file, constants, types, functions, NON_DEPRECATED_ONLY, false);
+    writeSummaryTables(&file, constants, types, functions, DEPRECATED_ONLY, false);
+
+    // Write the full details of each constant, type, and function.
+    if (!constants.empty()) {
+        file << "<h2>Constants</h2>\n";
+        for (auto i : constants) {
+            if (!writeDetailedConstant(&file, i.second)) {
+                success = false;
+            }
+        }
+    }
+    if (!types.empty()) {
+        file << "<h2>Types</h2>\n";
+        for (auto i : types) {
+            if (!writeDetailedType(&file, i.second)) {
+                success = false;
+            }
+        }
+    }
+    if (!functions.empty()) {
+        file << "<h2>Functions</h2>\n";
+        for (auto i : functions) {
+            if (!writeDetailedFunction(&file, i.second)) {
+                success = false;
+            }
+        }
+    }
+
+    writeFooter(&file, forVerification);
+    file.close();
+
+    if (!success) {
+        // If in error, write a final message to make it easier to figure out which file failed.
+        cerr << fileName << ": Failed due to errors.\n";
+    }
+    return success;
+}
+
+static void generateSnippet(GeneratedFile* file, const string& fileName, const string& title) {
+    const char offset[] = "                  ";
+    *file << offset << "<li><a href=\"<?cs var:toroot ?>guide/topics/renderscript/reference/"
+          << fileName << "\">\n";
+    *file << offset << "  <span class=\"en\">" << title << "</span>\n";
+    *file << offset << "</a></li>\n";
+}
+
+/* Generate a partial file of links that should be cut & pasted into the proper section of the
+ * guide_toc.cs file.
+ */
+static bool generateAndroidTableOfContentSnippet(const string& directory) {
+    GeneratedFile file;
+    if (!file.start(directory, "guide_toc.cs")) {
+        return false;
+    }
+    file << "<!-- Copy and paste the following lines into the RenderScript section of\n";
+    file << "     platform/frameworks/base/docs/html/guide/guide_toc.cs\n\n";
+
+    generateSnippet(&file, OVERVIEW_HTML_FILE_NAME, "Overview");
+    for (auto specFile : systemSpecification.getSpecFiles()) {
+        if (specFile->hasSpecifications()) {
+            const string fileName = stringReplace(specFile->getSpecFileName(), ".spec", ".html");
+            generateSnippet(&file, fileName, specFile->getBriefDescription());
+        }
+    }
+    generateSnippet(&file, INDEX_HTML_FILE_NAME, "Index");
+    return true;
+}
+
+bool generateDocumentation(const string& directory, bool forVerification) {
+    bool success = generateOverview(directory, forVerification) &&
+                   generateAlphabeticalIndex(directory, forVerification) &&
+                   generateAndroidTableOfContentSnippet(directory);
+    for (auto specFile : systemSpecification.getSpecFiles()) {
+        if (!writeDetailedDocumentationFile(directory, *specFile, forVerification)) {
+            success = false;
+        }
+    }
+    return success;
+}