Skip to content

Commit

Permalink
[FLINK-14494][docs] Add ConfigOption type to the documentation generator
Browse files Browse the repository at this point in the history
  • Loading branch information
dawidwys committed Nov 14, 2019
1 parent ee44dc5 commit ac8d508
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@
import org.apache.flink.configuration.ConfigOption;
import org.apache.flink.configuration.description.Formatter;
import org.apache.flink.configuration.description.HtmlFormatter;
import org.apache.flink.util.TimeUtils;
import org.apache.flink.util.function.ThrowingConsumer;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -47,6 +50,7 @@
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.apache.flink.docs.util.Utils.escapeCharacters;

Expand Down Expand Up @@ -158,6 +162,7 @@ private static void createTable(String rootDir, String module, String packageNam
});
}

@VisibleForTesting
static void processConfigOptions(String rootDir, String module, String packageName, String pathPrefix, ThrowingConsumer<Class<?>, IOException> classConsumer) throws IOException, ClassNotFoundException {
Path configDir = Paths.get(rootDir, module, pathPrefix, packageName.replaceAll("\\.", "/"));

Expand Down Expand Up @@ -202,6 +207,7 @@ static List<Tuple2<ConfigGroup, String>> generateTablesForClass(Class<?> options
return tables;
}

@VisibleForTesting
static List<OptionWithMetaInfo> extractConfigOptions(Class<?> clazz) {
try {
List<OptionWithMetaInfo> configOptions = new ArrayList<>(8);
Expand Down Expand Up @@ -241,7 +247,8 @@ private static String toHtmlTable(final List<OptionWithMetaInfo> options) {
htmlTable.append(" <tr>\n");
htmlTable.append(" <th class=\"text-left\" style=\"width: 20%\">Key</th>\n");
htmlTable.append(" <th class=\"text-left\" style=\"width: 15%\">Default</th>\n");
htmlTable.append(" <th class=\"text-left\" style=\"width: 65%\">Description</th>\n");
htmlTable.append(" <th class=\"text-left\" style=\"width: 10%\">Type</th>\n");
htmlTable.append(" <th class=\"text-left\" style=\"width: 55%\">Description</th>\n");
htmlTable.append(" </tr>\n");
htmlTable.append(" </thead>\n");
htmlTable.append(" <tbody>\n");
Expand All @@ -265,6 +272,7 @@ private static String toHtmlTable(final List<OptionWithMetaInfo> options) {
private static String toHtmlString(final OptionWithMetaInfo optionWithMetaInfo) {
ConfigOption<?> option = optionWithMetaInfo.option;
String defaultValue = stringifyDefault(optionWithMetaInfo);
String type = typeToHtml(optionWithMetaInfo);
Documentation.TableOption tableOption = optionWithMetaInfo.field.getAnnotation(Documentation.TableOption.class);
StringBuilder execModeStringBuilder = new StringBuilder();
if (tableOption != null) {
Expand All @@ -286,25 +294,108 @@ private static String toHtmlString(final OptionWithMetaInfo optionWithMetaInfo)
" <tr>\n" +
" <td><h5>" + escapeCharacters(option.key()) + "</h5>" + execModeStringBuilder.toString() + "</td>\n" +
" <td style=\"word-wrap: break-word;\">" + escapeCharacters(addWordBreakOpportunities(defaultValue)) + "</td>\n" +
" <td>" + type + "</td>\n" +
" <td>" + formatter.format(option.description()) + "</td>\n" +
" </tr>\n";
}

private static Class<?> getClazz(ConfigOption<?> option) {
try {
Method getClazzMethod = ConfigOption.class.getDeclaredMethod("getClazz");
getClazzMethod.setAccessible(true);
Class clazz = (Class) getClazzMethod.invoke(option);
getClazzMethod.setAccessible(false);
return clazz;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private static boolean isList(ConfigOption<?> option) {
try {
Method getClazzMethod = ConfigOption.class.getDeclaredMethod("isList");
getClazzMethod.setAccessible(true);
boolean isList = (boolean) getClazzMethod.invoke(option);
getClazzMethod.setAccessible(false);
return isList;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@VisibleForTesting
static String typeToHtml(OptionWithMetaInfo optionWithMetaInfo) {
ConfigOption<?> option = optionWithMetaInfo.option;
Class<?> clazz = getClazz(option);
boolean isList = isList(option);

if (clazz.isEnum()) {
return enumTypeToHtml(clazz, isList);
}

return atomicTypeToHtml(clazz, isList);
}

private static String atomicTypeToHtml(Class<?> clazz, boolean isList) {
String typeName = clazz.getSimpleName();

final String type;
if (isList) {
type = String.format("List<%s>", typeName);
} else {
type = typeName;
}

return escapeCharacters(type);
}

private static String enumTypeToHtml(Class<?> enumClazz, boolean isList) {
final String type;
if (isList) {
type = "List<Enum>";
} else {
type = "Enum";
}

return String.format(
"<p>%s</p>Possible values: %s",
escapeCharacters(type),
escapeCharacters(Arrays.toString(enumClazz.getEnumConstants())));
}

@VisibleForTesting
static String stringifyDefault(OptionWithMetaInfo optionWithMetaInfo) {
ConfigOption<?> option = optionWithMetaInfo.option;
Documentation.OverrideDefault overrideDocumentedDefault = optionWithMetaInfo.field.getAnnotation(Documentation.OverrideDefault.class);
if (overrideDocumentedDefault != null) {
return overrideDocumentedDefault.value();
} else {
Object value = option.defaultValue();
if (value instanceof String) {
if (((String) value).isEmpty()) {
return "(none)";
}
return "\"" + value + "\"";
return stringifyObject(value);
}
}

@SuppressWarnings("unchecked")
private static String stringifyObject(Object value) {
if (value instanceof String) {
if (((String) value).isEmpty()) {
return "(none)";
}
return value == null ? "(none)" : value.toString();
return "\"" + value + "\"";
} else if (value instanceof Duration) {
return TimeUtils.getStringInMillis((Duration) value);
} else if (value instanceof List) {
return ((List<Object>) value).stream()
.map(ConfigOptionsDocGenerator::stringifyObject)
.collect(Collectors.joining(";"));
} else if (value instanceof Map) {
return ((Map<String, String>) value)
.entrySet()
.stream()
.map(e -> String.format("%s:%s", e.getKey(), e.getValue()))
.collect(Collectors.joining(","));
}
return value == null ? "(none)" : value.toString();
}

private static String addWordBreakOpportunities(String value) {
Expand Down
Loading

0 comments on commit ac8d508

Please sign in to comment.