com.googlesource.gerrit.plugins.xdocs.formatter.FormatterUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.googlesource.gerrit.plugins.xdocs.formatter.FormatterUtil.java

Source

// Copyright (C) 2014 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.

package com.googlesource.gerrit.plugins.xdocs.formatter;

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 static org.apache.commons.lang.StringEscapeUtils.escapeHtml;

import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
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;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

@Singleton
public class FormatterUtil {
    private static final Logger log = LoggerFactory.getLogger(FormatterUtil.class);

    private final String pluginName;
    private final File baseDir;
    private final GitRepositoryManager repoManager;
    private final ProjectCache projectCache;
    private final Formatters formatters;
    private final Map<String, String> defaultCss;

    @Inject
    FormatterUtil(@PluginName String pluginName, @PluginData File baseDir, GitRepositoryManager repoManager,
            ProjectCache projectCache, Formatters formatters) {
        this.pluginName = pluginName;
        this.baseDir = baseDir;
        this.repoManager = repoManager;
        this.projectCache = projectCache;
        this.formatters = formatters;
        this.defaultCss = new HashMap<>();
    }

    /**
     * Returns the CSS from the file "<plugin-name>/<name>-<theme>.css" in the
     * refs/meta/config branch of the project.
     *
     * If theme is <code>null</code> or empty, the CSS from the file
     * "<plugin-name>/<name>.css" is returned.
     *
     * @param name the name of the file in the "<plugin-name>/" folder 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 CSS from the file; HTML characters are escaped;
     *         <code>null</code> if the file doesn't exist
     */
    public String getCss(String projectName, String name, String theme) {
        return Strings.isNullOrEmpty(theme) ? getCss(projectName, name) : getCss(projectName, name + "-" + theme);
    }

    /**
     * Returns the CSS from the file "<plugin-name>/<name>.css" in the
     * refs/meta/config branch of the project.
     *
     * @param name the name of the file in the "<plugin-name>/" folder without the
     *        ".css" file extension
     * @return the CSS from the file; HTML characters are escaped;
     *         <code>null</code> if the file doesn't exist
     */
    public String getCss(String projectName, String name) {
        return escapeHtml(getMetaConfigFile(projectName, name + ".css"));
    }

    /**
     * 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".
     *
     * If theme is <code>null</code> or empty, the CSS from the file
     * "<review-site>/data/<plugin-name>/css/<name>.css" is 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 CSS from the file; HTML characters are escaped;
     *         <code>null</code> if the file doesn't exist
     * @throws IOException thrown in case of an I/O Error while reading the CSS
     *         file
     */
    public String getGlobalCss(String name, String theme) throws IOException {
        return Strings.isNullOrEmpty(theme) ? getGlobalCss(name) : getGlobalCss(name + "-" + theme);
    }

    /**
     * Returns the CSS from the file
     * "<review-site>/data/<plugin-name>/css/<name>.css".
     *
     * @param name the name of the CSS file without the ".css" file extension
     * @return the CSS from the file; HTML characters are escaped;
     *         <code>null</code> if the file doesn't exist
     * @throws IOException thrown in case of an I/O Error while reading the CSS
     *         file
     */
    public String getGlobalCss(String name) throws IOException {
        Path p = Paths.get(baseDir.getAbsolutePath(), "css", name + ".css");
        if (Files.exists(p)) {
            byte[] css = Files.readAllBytes(p);
            return escapeHtml(new String(css, UTF_8));
        }
        return null;
    }

    public String applyCss(String html, String formatterName, String projectName) throws IOException {
        ConfigSection projectCfg = formatters.getFormatterConfig(formatterName, projectName);
        String cssName = formatterName.toLowerCase(Locale.US);
        String cssTheme = projectCfg.getString(KEY_CSS_THEME);
        String defaultCss = getDefaultCss(formatterName);
        String inheritedCss = getInheritedCss(projectName, formatterName, cssName, cssTheme);
        String projectCss = getCss(projectName, cssName, cssTheme);
        if (projectCfg.getBoolean(KEY_INHERIT_CSS, true)) {
            return insertCss(html, MoreObjects.firstNonNull(inheritedCss, defaultCss), projectCss);
        } else {
            return insertCss(html,
                    MoreObjects.firstNonNull(projectCss, MoreObjects.firstNonNull(inheritedCss, defaultCss)));
        }
    }

    private String getDefaultCss(String formatterName) throws IOException {
        String css = defaultCss.get(formatterName);
        if (css == null) {
            URL url = FormatterUtil.class.getResource(formatterName.toLowerCase(Locale.US) + ".css");
            if (url != null) {
                try (InputStream in = url.openStream();
                        TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) {
                    tmp.copy(in);
                    css = new String(tmp.toByteArray(), UTF_8);
                }
            } else {
                log.info(String.format("No default CSS for formatter '%s' found.", formatterName));
                css = "";
            }
            defaultCss.put(formatterName, css);
        }
        return css;
    }

    /**
     * Inserts the given CSS into the given HTML.
     *
     * @param html the HTML
     * @param css the CSS, may be <code>null</code>
     * @return the HTML that includes the CSS
     */
    public String insertCss(String html, String css) {
        if (html == null || css == null) {
            return html;
        }

        return insertCss(html, css, null);
    }

    /**
     * Inserts the given CSS's into the given HTML.
     *
     * @param html the HTML
     * @param css1 first CSS, may be <code>null</code>
     * @param css2 second CSS, may be <code>null</code>
     * @return the HTML that includes the CSS
     */
    public String insertCss(String html, String css1, String css2) {
        if (html == null || (css1 == null && css2 == null)) {
            return html;
        }

        int p = html.lastIndexOf("</body>");
        if (p > 0) {
            StringBuilder b = new StringBuilder();
            b.append(html.substring(0, p));
            if (css1 != null) {
                b.append("<style type=\"text/css\">\n");
                b.append(css1);
                b.append("</style>\n");
            }
            if (css2 != null) {
                b.append("<style type=\"text/css\">\n");
                b.append(css2);
                b.append("</style>\n");
            }
            b.append(html.substring(p));
            return b.toString();
        } else {
            return html;
        }
    }

    /**
     * Returns the content of the specified file from the "<plugin-name>/" folder
     * of the ref/meta/config branch.
     *
     * @param projectName the name of the project
     * @param fileName the name of the file in the "<plugin-name>/" folder
     * @return the file content, <code>null</code> if the file doesn't exist
     */
    public String getMetaConfigFile(String projectName, String fileName) {
        try (Repository repo = repoManager.openRepository(new Project.NameKey(projectName))) {
            try (RevWalk rw = new RevWalk(repo)) {
                ObjectId id = repo.resolve(RefNames.REFS_CONFIG);
                if (id == null) {
                    return null;
                }
                RevCommit commit = rw.parseCommit(id);
                RevTree tree = commit.getTree();
                try (TreeWalk tw = new TreeWalk(repo)) {
                    tw.addTree(tree);
                    tw.setRecursive(true);
                    tw.setFilter(PathFilter.create(pluginName + "/" + fileName));
                    if (!tw.next()) {
                        return null;
                    }
                    ObjectId objectId = tw.getObjectId(0);
                    ObjectLoader loader = repo.open(objectId);
                    byte[] raw = loader.getBytes(Integer.MAX_VALUE);
                    return new String(raw, UTF_8);
                }
            }
        } catch (IOException e) {
            return null;
        }
    }
}