DocGenerator.java
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 org.apache.doris.common.util;
import org.apache.doris.common.Config;
import org.apache.doris.common.ConfigBase.ConfField;
import org.apache.doris.common.LogUtils;
import org.apache.doris.qe.GlobalVariable;
import org.apache.doris.qe.SessionVariable;
import org.apache.doris.qe.VariableMgr;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.output.FileWriterWithEncoding;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
/**
* This class is used to generate doc for FE config and session variable.
* The doc is generated from Config.java and SessionVariable.java
*/
@Slf4j
public class DocGenerator {
private static final String PLACEHOLDER = "<--DOC_PLACEHOLDER-->";
private static final String[] TYPE = new String[] {"类型:", "Type: "};
private static final String[] DEFAULT_VALYUE = new String[] {"默认值:", "Default: "};
private static final String[] OPTIONS = new String[] {"可选值:", "Options: "};
private static final String[] CONF_MUTABLE = new String[] {"是否可动态修改:", "Mutable: "};
private static final String[] CONF_MASTER_ONLY = new String[] {"是否为 Master FE 节点独有的配置项:",
"Master only: "};
private static final String[] VAR_READ_ONLY = new String[] {"只读变量:", "Read Only: "};
private static final String[] VAR_GLOBAL_ONLY = new String[] {"仅全局变量:", "Global only: "};
private String configDocTemplatePath;
private String configDocTemplatePathCN;
private String configDocOutputPath;
private String configDocOutputPathCN;
private String sessionVariableDocTemplatePath;
private String sessionVariableDocTemplatePathCN;
private String sessionVariableDocOutputPath;
private String sessionVariableDocOutputPathCN;
private enum Lang {
CN(0),
EN(1);
private int idx;
Lang(int idx) {
this.idx = idx;
}
}
public DocGenerator(String configDocTemplatePath, String configDocTemplatePathCN,
String configDocOutputPath, String configDocOutputPathCN,
String sessionVariableDocTemplatePath, String sessionVariableDocTemplatePathCN,
String sessionVariableDocOutputPath, String sessionVariableDocOutputPathCN) {
this.configDocTemplatePath = configDocTemplatePath;
this.configDocTemplatePathCN = configDocTemplatePathCN;
this.configDocOutputPath = configDocOutputPath;
this.configDocOutputPathCN = configDocOutputPathCN;
this.sessionVariableDocOutputPath = sessionVariableDocOutputPath;
this.sessionVariableDocTemplatePathCN = sessionVariableDocTemplatePathCN;
this.sessionVariableDocTemplatePath = sessionVariableDocTemplatePath;
this.sessionVariableDocOutputPathCN = sessionVariableDocOutputPathCN;
}
public void generate() throws Exception {
generateConfigDoc();
generateSessionVariableDoc();
}
private void generateConfigDoc() throws Exception {
// 1. CN
String contentCN = readDocTemplate(this.configDocTemplatePathCN);
contentCN = contentCN.replace(PLACEHOLDER, genFEConfigDoc(Lang.CN));
// 2. EN
String content = readDocTemplate(this.configDocTemplatePath);
content = content.replace(PLACEHOLDER, genFEConfigDoc(Lang.EN));
// 3. write CN
writeDoc(contentCN, this.configDocOutputPathCN);
// 4. write EN
writeDoc(content, this.configDocOutputPath);
}
private void generateSessionVariableDoc() throws Exception {
// 1. CN
String contentCN = readDocTemplate(this.sessionVariableDocTemplatePathCN);
contentCN = contentCN.replace(PLACEHOLDER, genSessionVariableDoc(Lang.CN));
// 2. EN
String content = readDocTemplate(this.sessionVariableDocTemplatePath);
content = content.replace(PLACEHOLDER, genSessionVariableDoc(Lang.EN));
// 3. write CN
writeDoc(contentCN, this.sessionVariableDocOutputPathCN);
// 4. write EN
writeDoc(content, this.sessionVariableDocOutputPath);
}
private String readDocTemplate(String templatePath) throws Exception {
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(templatePath))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
private void writeDoc(String content, String outputPath) throws Exception {
try (BufferedWriter bw = new BufferedWriter(new FileWriterWithEncoding(outputPath, StandardCharsets.UTF_8))) {
bw.write(content);
}
}
// generate doc for FE configs.
// Content will be sorted by config name.
private String genFEConfigDoc(Lang lang) throws IllegalAccessException {
Map<String, String> sortedDoc = Maps.newTreeMap();
Class confClass = Config.class;
for (Field field : confClass.getFields()) {
try {
String res = genSingleConfFieldDoc(field, lang);
if (!Strings.isNullOrEmpty(res)) {
sortedDoc.put(field.getName(), res);
}
} catch (Exception e) {
LogUtils.stderr("Failed to generate doc for field: " + field.getName());
throw e;
}
}
return printSortedMap(sortedDoc);
}
// Generate doc for a single config field.
private String genSingleConfFieldDoc(Field field, Lang lang) throws IllegalAccessException {
StringBuilder sb = new StringBuilder();
ConfField confField = field.getAnnotation(ConfField.class);
if (confField == null) {
return null;
}
String configName = confField.varType().getPrefix() + field.getName();
sb.append("### `").append(configName).append("`\n\n");
sb.append(confField.description()[lang.idx]).append("\n\n");
sb.append(TYPE[lang.idx]).append("`").append(field.getType().getSimpleName()).append("`\n\n");
sb.append(DEFAULT_VALYUE[lang.idx]).append("`").append(getStringValue(field, null)).append("`\n\n");
if (confField.options().length > 0) {
sb.append(OPTIONS[lang.idx]);
for (int i = 0; i < confField.options().length; i++) {
sb.append("`").append(confField.options()[i]).append("`");
if (i != confField.options().length - 1) {
sb.append(", ");
}
}
sb.append("\n\n");
}
sb.append(CONF_MUTABLE[lang.idx]).append("`").append(confField.mutable()).append("`\n\n");
sb.append(CONF_MASTER_ONLY[lang.idx]).append("`").append(confField.masterOnly()).append("`\n\n");
return sb.toString();
}
private static String getStringValue(Field field, Object instance) throws IllegalAccessException {
if (field.getType().isArray()) {
return Arrays.toString((Object[]) field.get(instance));
} else {
return String.valueOf(field.get(instance));
}
}
// generate doc for Session Variables
// Content will be sorted by variables' name.
private String genSessionVariableDoc(Lang lang) throws IllegalAccessException {
Map<String, String> sortedDoc = Maps.newTreeMap();
// 1. session variables
SessionVariable sv = new SessionVariable();
Class svClass = SessionVariable.class;
for (Field field : svClass.getFields()) {
try {
String res = genSingleSessionVariableDoc(sv, field, lang);
if (!Strings.isNullOrEmpty(res)) {
sortedDoc.put(field.getAnnotation(VariableMgr.VarAttr.class).name(), res);
}
} catch (Exception e) {
LogUtils.stderr("Failed to generate doc for " + field.getName());
throw e;
}
}
// 2. global variables
Class gvClass = GlobalVariable.class;
for (Field field : gvClass.getFields()) {
try {
String res = genSingleSessionVariableDoc(null, field, lang);
if (!Strings.isNullOrEmpty(res)) {
sortedDoc.put(field.getAnnotation(VariableMgr.VarAttr.class).name(), res);
}
} catch (Exception e) {
LogUtils.stderr("Failed to generate doc for field: " + field.getName());
throw e;
}
}
return printSortedMap(sortedDoc);
}
@NotNull
private static String printSortedMap(Map<String, String> sortedDoc) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedDoc.entrySet()) {
sb.append(entry.getValue());
}
return sb.toString();
}
private String genSingleSessionVariableDoc(SessionVariable sv, Field field, Lang lang)
throws IllegalAccessException {
VariableMgr.VarAttr varAttr = field.getAnnotation(VariableMgr.VarAttr.class);
if (varAttr == null) {
return null;
}
StringBuilder sb = new StringBuilder();
String varName = varAttr.varType().getPrefix() + varAttr.name();
sb.append("### `").append(varName).append("`\n\n");
sb.append(varAttr.description()[lang.idx]).append("\n\n");
sb.append(TYPE[lang.idx]).append("`").append(field.getType().getSimpleName()).append("`\n\n");
sb.append(DEFAULT_VALYUE[lang.idx]).append("`").append(getStringValue(field, sv)).append("`\n\n");
if (varAttr.options().length > 0) {
sb.append(OPTIONS[lang.idx]);
for (int i = 0; i < varAttr.options().length; i++) {
sb.append("`").append(varAttr.options()[i]).append("`");
if (i != varAttr.options().length - 1) {
sb.append(", ");
}
}
sb.append("\n\n");
}
sb.append(VAR_READ_ONLY[lang.idx]).append("`")
.append((varAttr.flag() & VariableMgr.READ_ONLY) != 0).append("`\n\n");
sb.append(VAR_GLOBAL_ONLY[lang.idx]).append("`")
.append((varAttr.flag() & VariableMgr.GLOBAL) != 0).append("`\n\n");
return sb.toString();
}
/**
* generate config and session variable doc from given templates
*
* @param args args[0]: config doc template path
* args[1]: config doc template path CN
* args[2]: config doc output path
* args[3]: config doc output path CN
* args[4]: session variable doc template path
* args[5]: session variable doc template path CN
* args[6]: session variable doc output path
* args[7]: session variable doc output path CN
*/
public static void main(String[] args) {
String configDocTemplatePath = args[0];
String configDocTemplatePathCN = args[1];
String configDocOutputPath = args[2];
String configDocOutputPathCN = args[3];
String sessionVariableDocTemplatePath = args[4];
String sessionVariableDocTemplatePathCN = args[5];
String sessionVariableDocOutputPath = args[6];
String sessionVariableDocOutputPathCN = args[7];
DocGenerator docGenerator = new DocGenerator(
configDocTemplatePath, configDocTemplatePathCN,
configDocOutputPath, configDocOutputPathCN,
sessionVariableDocTemplatePath, sessionVariableDocTemplatePathCN,
sessionVariableDocOutputPath, sessionVariableDocOutputPathCN);
try {
docGenerator.generate();
LogUtils.stdout("Done!");
} catch (Exception e) {
log.info("failed to generate doc", e);
System.exit(-1);
}
}
}