Support inheriting of CSS

Change-Id: Ib04c452561d1204dc3b392238f52193d43a09f52
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
index 4d859b5..e84e1e4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
@@ -23,12 +23,12 @@
 public class XDocGlobalConfig {
   public static final String SECTION_FORMATTER = "formatter";
   public static final String KEY_ALLOW_HTML = "allowHtml";
-  public static final String KEY_APPEND_CSS = "appendCss";
   public static final String KEY_CSS_THEME = "cssTheme";
   public static final String KEY_ENABLED = "enabled";
   public static final String KEY_EXT = "ext";
   public static final String KEY_FORMATTER = "formatter";
   public static final String KEY_INCLUDE_TOC = "includeToc";
+  public static final String KEY_INHERIT_CSS = "inheritCss";
   public static final String KEY_MIME_TYPE = "mimeType";
   public static final String KEY_PREFIX = "prefix";
   public static final String KEY_PRIO = "prio";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
index fe57a90..8f86ccf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
@@ -15,9 +15,9 @@
 package com.googlesource.gerrit.plugins.xdocs.formatter;
 
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_ALLOW_HTML;
-import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_APPEND_CSS;
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_CSS_THEME;
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INCLUDE_TOC;
+import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INHERIT_CSS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.MoreObjects;
@@ -80,7 +80,7 @@
     }
 
     ConfigSection projectCfg =
-        formatters.getFormatterConfig(globalCfg.getSubsection(), projectName);
+        formatters.getFormatterConfig(NAME, projectName);
     // asciidoctor ignores all attributes if no output file is specified,
     // this is why we must specify an output file and then read its content
     File tmpFile =
@@ -93,15 +93,16 @@
         ByteStreams.copy(input, out);
         String html = out.toString(UTF_8.name());
         String cssTheme = projectCfg.getString(KEY_CSS_THEME);
-        String globalCss = util.getGlobalCss("asciidoctor", cssTheme);
+        String inheritedCss =
+            util.getInheritedCss(projectName, NAME, "asciidoctor", cssTheme);
         String projectCss = util.getCss(projectName, "asciidoctor", cssTheme);
-        if (projectCfg.getBoolean(KEY_APPEND_CSS, true)) {
+        if (projectCfg.getBoolean(KEY_INHERIT_CSS, true)) {
           return util.insertCss(html,
-              MoreObjects.firstNonNull(globalCss, defaultCss), projectCss);
+              MoreObjects.firstNonNull(inheritedCss, defaultCss), projectCss);
         } else {
           return util.insertCss(html,
               MoreObjects.firstNonNull(projectCss,
-                  MoreObjects.firstNonNull(globalCss, defaultCss)));
+                  MoreObjects.firstNonNull(inheritedCss, defaultCss)));
         }
       }
     } finally {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
index 47aedf4..2c49704 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
@@ -14,18 +14,24 @@
 
 package com.googlesource.gerrit.plugins.xdocs.formatter;
 
+import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INHERIT_CSS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
+
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.Repository;
@@ -46,14 +52,20 @@
   private final String pluginName;
   private final File baseDir;
   private final GitRepositoryManager repoManager;
+  private final ProjectCache projectCache;
+  private final Formatters formatters;
 
   @Inject
   FormatterUtil(@PluginName String pluginName,
       @PluginData File baseDir,
-      GitRepositoryManager repoManager) {
+      GitRepositoryManager repoManager,
+      ProjectCache projectCache,
+      Formatters formatters) {
     this.pluginName = pluginName;
     this.baseDir = baseDir;
     this.repoManager = repoManager;
+    this.projectCache = projectCache;
+    this.formatters = formatters;
   }
 
   /**
@@ -90,6 +102,55 @@
   }
 
   /**
+   * Returns the inherited CSS.
+   *
+   * If the project has a parent project the CSS of the parent project is
+   * returned; if there is no parent project the global CSS is returned.
+   *
+   * @param projectName the name of the project
+   * @param formatterName the name of the formatter for which the CSS should be
+   *        returned
+   * @param name the name of the CSS file without theme and without the ".css"
+   *        file extension
+   * @param theme the name of the CSS theme, may be <code>null</code>, if given
+   *        it is included into the CSS file name: '<name>-<theme>.css'
+   * @return the inherited CSS; HTML characters are escaped; <code>null</code>
+   *         if there is no inherited CSS
+   * @throws IOException thrown in case of an I/O Error while reading the global
+   *         CSS file
+   */
+  public String getInheritedCss(String projectName, String formatterName,
+      String name, String theme) throws IOException {
+    return getInheritedCss(projectCache.get(new Project.NameKey(projectName)),
+        formatterName, name, theme);
+  }
+
+  private String getInheritedCss(ProjectState project, String formatterName,
+    String name, String theme) throws IOException {
+    for (ProjectState parent : project.parents()) {
+      String css = getCss(parent.getProject().getName(), name, theme);
+      ConfigSection cfg =
+          formatters.getFormatterConfig(formatterName, parent);
+      if (cfg.getBoolean(KEY_INHERIT_CSS, true)) {
+        return joinCss(getInheritedCss(parent, formatterName, name, theme), css);
+      } else {
+        return css;
+      }
+    }
+    return getGlobalCss(name, theme);
+  }
+
+  private String joinCss(String css1, String css2) {
+    if (css1 == null) {
+      return css2;
+    }
+    if (css2 == null) {
+      return css1;
+    }
+    return Joiner.on('\n').join(css1, css2);
+  }
+
+  /**
    * Returns the CSS from the file
    * "<review-site>/data/<plugin-name>/css/<name>-<theme>.css".
    *
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java
index f4aa3a8..5ce5a73 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/MarkdownFormatter.java
@@ -15,8 +15,8 @@
 package com.googlesource.gerrit.plugins.xdocs.formatter;
 
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_ALLOW_HTML;
-import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_APPEND_CSS;
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_CSS_THEME;
+import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INHERIT_CSS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.inject.Inject;
@@ -43,30 +43,31 @@
   public String format(String projectName, String revision,
       ConfigSection globalCfg, String raw) throws IOException {
     ConfigSection projectCfg =
-        formatters.getFormatterConfig(globalCfg.getSubsection(), projectName);
+        formatters.getFormatterConfig(NAME, projectName);
     com.google.gerrit.server.documentation.MarkdownFormatter f =
         new com.google.gerrit.server.documentation.MarkdownFormatter();
     if (!globalCfg.getBoolean(KEY_ALLOW_HTML, false)) {
       f.suppressHtml();
     }
     String cssTheme = projectCfg.getString(KEY_CSS_THEME);
-    String globalCss = util.getGlobalCss("markdown", cssTheme);
+    String inheritedCss =
+        util.getInheritedCss(projectName, NAME, "markdown", cssTheme);
     String projectCss = util.getCss(projectName, "markdown", cssTheme);
-    if (projectCfg.getBoolean(KEY_APPEND_CSS, true)) {
-      // if there is no global CSS and f.setCss(null) is invoked
+    if (projectCfg.getBoolean(KEY_INHERIT_CSS, true)) {
+      // if there is no inherited CSS and f.setCss(null) is invoked
       // com.google.gerrit.server.documentation.MarkdownFormatter applies the
       // default CSS
-      f.setCss(globalCss);
+      f.setCss(inheritedCss);
       byte[] b = f.markdownToDocHtml(raw, UTF_8.name());
       return util.insertCss(new String(b, UTF_8), projectCss);
     } else {
       if (projectCss != null) {
         f.setCss(projectCss);
       } else {
-        // if there is no global CSS and f.setCss(null) is invoked
+        // if there is no inherited CSS and f.setCss(null) is invoked
         // com.google.gerrit.server.documentation.MarkdownFormatter applies the
         // default CSS
-        f.setCss(globalCss);
+        f.setCss(inheritedCss);
       }
       byte[] b = f.markdownToDocHtml(raw, UTF_8.name());
       return new String(b, UTF_8);
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 9cf7e1b..26d773e 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -86,19 +86,19 @@
 
 	Default: `true`
 
-<a id="appendCss">
-formatter.<formatter>.appendCss
-:	Whether the project-specific CSS should be appended to the default CSS.
+<a id="inheritCss">
+formatter.<formatter>.inheritCss
+:	Whether the CSS should be inherited or overridden.
 
 	If `false` the default CSS is completely replaced by the
 	project-specific CSS.
 
-	Overrides the [global configuration of `appendCss`](#formatterAppendCss)
+	Overrides the [global configuration of `inheritCss`](#formatterInheritCss)
 	for this formatter.
 
 	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
 
-	Default: `true` (project-specific CSS is appended to the default CSS)
+	Default: `true` (CSS is inherited)
 
 <a id="cssTheme">
 formatter.<formatter>.cssTheme
@@ -138,7 +138,8 @@
 * `ASCIIDOCTOR`: `@PLUGIN@/asciidoctor.css`
 * `MARKDOWN`: `@PLUGIN@/markdown.css`
 
-Custom CSS files are *NOT* inherited from parent projects.
+If link:inheritCss[inheritCss] is set to true custom CSS files are
+inherited from parent projects.
 
 <a id="globalConfig">
 Global Configuration
@@ -233,18 +234,18 @@
 
 	Default: `true`
 
-<a id="formatterAppendCss">
-formatter.<formatter>.appendCss
-:	Whether project-specific CSS should be appended to the default CSS.
+<a id="formatterInheritCss">
+formatter.<formatter>.inheritCss
+:	Whether CSS should be inherited or overridden.
 
 	If `false` the default CSS is completely replaced by the
 	project-specific CSS.
 
-	Can be overridden on [project-level](#appendCss).
+	Can be overridden on [project-level](#inheritCss).
 
 	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
 
-	Default: `true` (project-specific CSS is appended to the default CSS)
+	Default: `true` (CSS is inherited)
 
 <a id="formatterCssTheme">
 formatter.<formatter>.cssTheme