/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.painless; import org.opensearch.common.SuppressForbidden; import org.opensearch.common.io.PathUtils; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.common.util.io.IOUtils; import org.opensearch.painless.action.PainlessContextClassBindingInfo; import org.opensearch.painless.action.PainlessContextClassInfo; import org.opensearch.painless.action.PainlessContextConstructorInfo; import org.opensearch.painless.action.PainlessContextFieldInfo; import org.opensearch.painless.action.PainlessContextInfo; import org.opensearch.painless.action.PainlessContextInstanceBindingInfo; import org.opensearch.painless.action.PainlessContextMethodInfo; import java.io.IOException; import java.io.PrintStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * The gradle task generateContextDoc uses ContextDocGenerator to rebuild * the Painless API documentation from a clean state after the * existing documentation is deleted. The following pages are generated: * * Use the docs build to generate HTML pages from the resultant asciidoc files. */ public final class ContextDocGenerator { private static final String SHARED_HEADER = "painless-api-reference-shared"; private static final String SHARED_NAME = "Shared"; public static void main(String[] args) throws IOException { List contextInfos = getContextInfos(); Set sharedStaticInfos = createSharedStatics(contextInfos); Set sharedClassInfos = createSharedClasses(contextInfos); Path rootDir = resetRootDir(); Path sharedDir = createSharedDir(rootDir); List staticInfos = sortStaticInfos(Collections.emptySet(), new ArrayList<>(sharedStaticInfos)); List classInfos = sortClassInfos(Collections.emptySet(), new ArrayList<>(sharedClassInfos)); Map javaNamesToDisplayNames = getDisplayNames(classInfos); printSharedIndexPage(sharedDir, javaNamesToDisplayNames, staticInfos, classInfos); printSharedPackagesPages(sharedDir, javaNamesToDisplayNames, classInfos); Set isSpecialized = new HashSet<>(); for (PainlessContextInfo contextInfo : contextInfos) { staticInfos = createContextStatics(contextInfo); staticInfos = sortStaticInfos(sharedStaticInfos, staticInfos); classInfos = sortClassInfos(sharedClassInfos, new ArrayList<>(contextInfo.getClasses())); if (staticInfos.isEmpty() == false || classInfos.isEmpty() == false) { Path contextDir = createContextDir(rootDir, contextInfo); isSpecialized.add(contextInfo); javaNamesToDisplayNames = getDisplayNames(contextInfo.getClasses()); printContextIndexPage(contextDir, javaNamesToDisplayNames, contextInfo, staticInfos, classInfos); printContextPackagesPages(contextDir, javaNamesToDisplayNames, sharedClassInfos, contextInfo, classInfos); } } printRootIndexPage(rootDir, contextInfos, isSpecialized); } @SuppressForbidden(reason = "retrieving data from an internal API not exposed as part of the REST client") private static List getContextInfos() throws IOException { URLConnection getContextNames = new URL("http://" + System.getProperty("cluster.uri") + "/_scripts/painless/_context") .openConnection(); XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, getContextNames.getInputStream()); parser.nextToken(); parser.nextToken(); @SuppressWarnings("unchecked") List contextNames = (List) (Object) parser.list(); parser.close(); ((HttpURLConnection) getContextNames).disconnect(); List contextInfos = new ArrayList<>(); for (String contextName : contextNames) { URLConnection getContextInfo = new URL( "http://" + System.getProperty("cluster.uri") + "/_scripts/painless/_context?context=" + contextName ).openConnection(); parser = JsonXContent.jsonXContent.createParser(null, null, getContextInfo.getInputStream()); contextInfos.add(PainlessContextInfo.fromXContent(parser)); ((HttpURLConnection) getContextInfo).disconnect(); } contextInfos.sort(Comparator.comparing(PainlessContextInfo::getName)); return contextInfos; } private static Set createSharedStatics(List contextInfos) { Map staticInfoCounts = new HashMap<>(); for (PainlessContextInfo contextInfo : contextInfos) { for (PainlessContextMethodInfo methodInfo : contextInfo.getImportedMethods()) { staticInfoCounts.merge(methodInfo, 1, Integer::sum); } for (PainlessContextClassBindingInfo classBindingInfo : contextInfo.getClassBindings()) { staticInfoCounts.merge(classBindingInfo, 1, Integer::sum); } for (PainlessContextInstanceBindingInfo instanceBindingInfo : contextInfo.getInstanceBindings()) { staticInfoCounts.merge(instanceBindingInfo, 1, Integer::sum); } } return staticInfoCounts.entrySet() .stream() .filter(e -> e.getValue() == contextInfos.size()) .map(Map.Entry::getKey) .collect(Collectors.toSet()); } private static List createContextStatics(PainlessContextInfo contextInfo) { List staticInfos = new ArrayList<>(); staticInfos.addAll(contextInfo.getImportedMethods()); staticInfos.addAll(contextInfo.getClassBindings()); staticInfos.addAll(contextInfo.getInstanceBindings()); return staticInfos; } private static Set createSharedClasses(List contextInfos) { Map classInfoCounts = new HashMap<>(); for (PainlessContextInfo contextInfo : contextInfos) { for (PainlessContextClassInfo classInfo : contextInfo.getClasses()) { classInfoCounts.merge(classInfo, 1, Integer::sum); } } return classInfoCounts.entrySet() .stream() .filter(e -> e.getValue() == contextInfos.size()) .map(Map.Entry::getKey) .collect(Collectors.toSet()); } @SuppressForbidden(reason = "resolve api docs directory with environment") private static Path resetRootDir() throws IOException { Path rootDir = PathUtils.get("../../docs/painless/painless-api-reference"); IOUtils.rm(rootDir); Files.createDirectories(rootDir); return rootDir; } private static Path createSharedDir(Path rootDir) throws IOException { Path sharedDir = rootDir.resolve(SHARED_HEADER); Files.createDirectories(sharedDir); return sharedDir; } private static Path createContextDir(Path rootDir, PainlessContextInfo info) throws IOException { Path contextDir = rootDir.resolve(getContextHeader(info)); Files.createDirectories(contextDir); return contextDir; } private static void printAutomatedMessage(PrintStream stream) { stream.println("// This file is auto-generated. Do not edit."); stream.println(); } private static void printSharedIndexPage( Path sharedDir, Map javaNamesToDisplayNames, List staticInfos, List classInfos ) throws IOException { Path sharedIndexPath = sharedDir.resolve("index.asciidoc"); try ( PrintStream sharedIndexStream = new PrintStream( Files.newOutputStream(sharedIndexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name() ) ) { printAutomatedMessage(sharedIndexStream); sharedIndexStream.println("[[" + SHARED_HEADER + "]]"); sharedIndexStream.println("=== " + SHARED_NAME + " API"); sharedIndexStream.println(); sharedIndexStream.println("The following API is available in all contexts."); printIndex(sharedIndexStream, SHARED_HEADER, javaNamesToDisplayNames, staticInfos, classInfos); } } private static void printContextIndexPage( Path contextDir, Map javaNamesToDisplayNames, PainlessContextInfo contextInfo, List staticInfos, List classInfos ) throws IOException { Path contextIndexPath = contextDir.resolve("index.asciidoc"); try ( PrintStream contextIndexStream = new PrintStream( Files.newOutputStream(contextIndexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name() ) ) { printAutomatedMessage(contextIndexStream); contextIndexStream.println("[[" + getContextHeader(contextInfo) + "]]"); contextIndexStream.println("=== " + getContextName(contextInfo) + " API"); contextIndexStream.println(); contextIndexStream.println("The following specialized API is available in the " + getContextName(contextInfo) + " context."); contextIndexStream.println(); contextIndexStream.println( "* See the <<" + SHARED_HEADER + ", " + SHARED_NAME + " API>> for further API available in all contexts." ); printIndex(contextIndexStream, getContextHeader(contextInfo), javaNamesToDisplayNames, staticInfos, classInfos); } } private static void printIndex( PrintStream indexStream, String contextHeader, Map javaNamesToDisplayNames, List staticInfos, List classInfos ) { String currentPackageName = null; if (staticInfos.isEmpty() == false) { indexStream.println(); indexStream.println("==== Static Methods"); indexStream.println( "The following methods are directly callable without a class/instance qualifier. " + "Note parameters denoted by a (*) are treated as read-only values." ); indexStream.println(); for (Object staticInfo : staticInfos) { if (staticInfo instanceof PainlessContextMethodInfo) { printMethod(indexStream, javaNamesToDisplayNames, false, (PainlessContextMethodInfo) staticInfo); } else if (staticInfo instanceof PainlessContextClassBindingInfo) { printClassBinding(indexStream, javaNamesToDisplayNames, (PainlessContextClassBindingInfo) staticInfo); } else if (staticInfo instanceof PainlessContextInstanceBindingInfo) { printInstanceBinding(indexStream, javaNamesToDisplayNames, (PainlessContextInstanceBindingInfo) staticInfo); } else { throw new IllegalArgumentException("unexpected static info type"); } } } if (classInfos.isEmpty() == false) { indexStream.println(); indexStream.println("==== Classes By Package"); indexStream.println( "The following classes are available grouped by their respective packages. Click on a class " + "to view details about the available methods and fields." ); indexStream.println(); for (PainlessContextClassInfo classInfo : classInfos) { String classPackageName = classInfo.getName().substring(0, classInfo.getName().lastIndexOf('.')); if (classPackageName.equals(currentPackageName) == false) { currentPackageName = classPackageName; indexStream.println(); indexStream.println("==== " + currentPackageName); indexStream.println( "<<" + getPackageHeader(contextHeader, currentPackageName) + ", " + "Expand details for " + currentPackageName + ">>" ); indexStream.println(); } String className = getType(javaNamesToDisplayNames, classInfo.getName()); indexStream.println("* <<" + getClassHeader(contextHeader, className) + ", " + className + ">>"); } } indexStream.println(); indexStream.println("include::packages.asciidoc[]"); indexStream.println(); } private static void printSharedPackagesPages( Path sharedDir, Map javaNamesToDisplayNames, List classInfos ) throws IOException { Path sharedClassesPath = sharedDir.resolve("packages.asciidoc"); try ( PrintStream sharedPackagesStream = new PrintStream( Files.newOutputStream(sharedClassesPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name() ) ) { printAutomatedMessage(sharedPackagesStream); printPackages(sharedPackagesStream, SHARED_NAME, SHARED_HEADER, javaNamesToDisplayNames, Collections.emptySet(), classInfos); } } private static void printContextPackagesPages( Path contextDir, Map javaNamesToDisplayNames, Set excludes, PainlessContextInfo contextInfo, List classInfos ) throws IOException { Path contextPackagesPath = contextDir.resolve("packages.asciidoc"); try ( PrintStream contextPackagesStream = new PrintStream( Files.newOutputStream(contextPackagesPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name() ) ) { printAutomatedMessage(contextPackagesStream); printPackages( contextPackagesStream, getContextName(contextInfo), getContextHeader(contextInfo), javaNamesToDisplayNames, excludes, classInfos ); } } private static void printPackages( PrintStream packagesStream, String contextName, String contextHeader, Map javaNamesToDisplayNames, Set excludes, List classInfos ) { String currentPackageName = null; for (PainlessContextClassInfo classInfo : classInfos) { if (excludes.contains(classInfo)) { continue; } String classPackageName = classInfo.getName().substring(0, classInfo.getName().lastIndexOf('.')); if (classPackageName.equals(currentPackageName) == false) { currentPackageName = classPackageName; packagesStream.println(); packagesStream.println("[role=\"exclude\",id=\"" + getPackageHeader(contextHeader, currentPackageName) + "\"]"); packagesStream.println("=== " + contextName + " API for package " + currentPackageName); packagesStream.println( "See the <<" + contextHeader + ", " + contextName + " API>> " + "for a high-level overview of all packages and classes." ); } String className = getType(javaNamesToDisplayNames, classInfo.getName()); packagesStream.println(); packagesStream.println("[[" + getClassHeader(contextHeader, className) + "]]"); packagesStream.println("==== " + className + ""); for (PainlessContextFieldInfo fieldInfo : classInfo.getStaticFields()) { printField(packagesStream, javaNamesToDisplayNames, true, fieldInfo); } for (PainlessContextMethodInfo methodInfo : classInfo.getStaticMethods()) { printMethod(packagesStream, javaNamesToDisplayNames, true, methodInfo); } for (PainlessContextFieldInfo fieldInfo : classInfo.getFields()) { printField(packagesStream, javaNamesToDisplayNames, false, fieldInfo); } for (PainlessContextConstructorInfo constructorInfo : classInfo.getConstructors()) { printConstructor(packagesStream, javaNamesToDisplayNames, className, constructorInfo); } for (PainlessContextMethodInfo methodInfo : classInfo.getMethods()) { printMethod(packagesStream, javaNamesToDisplayNames, false, methodInfo); } packagesStream.println(); } packagesStream.println(); } private static void printRootIndexPage(Path rootDir, List contextInfos, Set isSpecialized) throws IOException { Path rootIndexPath = rootDir.resolve("index.asciidoc"); try ( PrintStream rootIndexStream = new PrintStream( Files.newOutputStream(rootIndexPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), false, StandardCharsets.UTF_8.name() ) ) { printAutomatedMessage(rootIndexStream); rootIndexStream.println("[cols=\"<3,^3,^3\"]"); rootIndexStream.println("|===="); for (PainlessContextInfo contextInfo : contextInfos) { String contextName = getContextName(contextInfo); String contextHeader = getContextHeader(contextInfo); rootIndexStream.print("|" + contextName + " "); rootIndexStream.print("| <<" + SHARED_HEADER + ", " + SHARED_NAME + " API>> "); if (isSpecialized.contains(contextInfo)) { rootIndexStream.println("| <<" + contextHeader + ", Specialized API>>"); } else { rootIndexStream.println("| "); } } rootIndexStream.println("|===="); rootIndexStream.println(); rootIndexStream.println("include::" + SHARED_HEADER + "/index.asciidoc[]"); for (PainlessContextInfo contextInfo : contextInfos) { if (isSpecialized.contains(contextInfo)) { rootIndexStream.println("include::" + getContextHeader(contextInfo) + "/index.asciidoc[]"); } } } } private static void printConstructor( PrintStream stream, Map javaNamesToDisplayNames, String className, PainlessContextConstructorInfo constructorInfo ) { stream.print("* "); if (constructorInfo.getDeclaring().startsWith("java.")) { stream.print(getConstructorJavaDocLink(constructorInfo) + "[" + className + "]"); } else { stream.print(className); } stream.print("("); for (int parameterIndex = 0; parameterIndex < constructorInfo.getParameters().size(); ++parameterIndex) { stream.print(getType(javaNamesToDisplayNames, constructorInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < constructorInfo.getParameters().size()) { stream.print(", "); } } stream.println(")"); } private static void printMethod( PrintStream stream, Map javaNamesToDisplayNames, boolean isStatic, PainlessContextMethodInfo methodInfo ) { stream.print("* " + (isStatic ? "static " : "")); stream.print(getType(javaNamesToDisplayNames, methodInfo.getRtn()) + " "); if (methodInfo.getDeclaring().startsWith("java.")) { stream.print(getMethodJavaDocLink(methodInfo) + "[" + methodInfo.getName() + "]"); } else { stream.print(methodInfo.getName()); } stream.print("("); for (int parameterIndex = 0; parameterIndex < methodInfo.getParameters().size(); ++parameterIndex) { stream.print(getType(javaNamesToDisplayNames, methodInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < methodInfo.getParameters().size()) { stream.print(", "); } } stream.println(")"); } private static void printClassBinding( PrintStream stream, Map javaNamesToDisplayNames, PainlessContextClassBindingInfo classBindingInfo ) { stream.print("* " + getType(javaNamesToDisplayNames, classBindingInfo.getRtn()) + " " + classBindingInfo.getName() + "("); for (int parameterIndex = 0; parameterIndex < classBindingInfo.getParameters().size(); ++parameterIndex) { // temporary fix to not print org.opensearch.script.ScoreScript parameter until // class instance bindings are created and the information is appropriately added to the context info classes if ("org.opensearch.script.ScoreScript".equals( getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex)) )) { continue; } stream.print(getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex))); if (parameterIndex < classBindingInfo.getReadOnly()) { stream.print(" *"); } if (parameterIndex + 1 < classBindingInfo.getParameters().size()) { stream.print(", "); } } stream.println(")"); } private static void printInstanceBinding( PrintStream stream, Map javaNamesToDisplayNames, PainlessContextInstanceBindingInfo instanceBindingInfo ) { stream.print("* " + getType(javaNamesToDisplayNames, instanceBindingInfo.getRtn()) + " " + instanceBindingInfo.getName() + "("); for (int parameterIndex = 0; parameterIndex < instanceBindingInfo.getParameters().size(); ++parameterIndex) { stream.print(getType(javaNamesToDisplayNames, instanceBindingInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < instanceBindingInfo.getParameters().size()) { stream.print(", "); } } stream.println(")"); } private static void printField( PrintStream stream, Map javaNamesToDisplayNames, boolean isStatic, PainlessContextFieldInfo fieldInfo ) { stream.print("* " + (isStatic ? "static " : "")); stream.print(getType(javaNamesToDisplayNames, fieldInfo.getType()) + " "); if (fieldInfo.getDeclaring().startsWith("java.")) { stream.println(getFieldJavaDocLink(fieldInfo) + "[" + fieldInfo.getName() + "]"); } else { stream.println(fieldInfo.getName()); } } private static String getType(Map javaNamesToDisplayNames, String javaType) { int arrayDimensions = 0; while (javaType.charAt(arrayDimensions) == '[') { ++arrayDimensions; } if (arrayDimensions > 0) { if (javaType.charAt(javaType.length() - 1) == ';') { javaType = javaType.substring(arrayDimensions + 1, javaType.length() - 1); } else { javaType = javaType.substring(arrayDimensions); } } if ("Z".equals(javaType) || "boolean".equals(javaType)) { javaType = "boolean"; } else if ("V".equals(javaType) || "void".equals(javaType)) { javaType = "void"; } else if ("B".equals(javaType) || "byte".equals(javaType)) { javaType = "byte"; } else if ("S".equals(javaType) || "short".equals(javaType)) { javaType = "short"; } else if ("C".equals(javaType) || "char".equals(javaType)) { javaType = "char"; } else if ("I".equals(javaType) || "int".equals(javaType)) { javaType = "int"; } else if ("J".equals(javaType) || "long".equals(javaType)) { javaType = "long"; } else if ("F".equals(javaType) || "float".equals(javaType)) { javaType = "float"; } else if ("D".equals(javaType) || "double".equals(javaType)) { javaType = "double"; } else if ("org.opensearch.painless.lookup.def".equals(javaType)) { javaType = "def"; } else { javaType = javaNamesToDisplayNames.get(javaType); } while (arrayDimensions-- > 0) { javaType += "[]"; } return javaType; } private static String getFieldJavaDocLink(PainlessContextFieldInfo fieldInfo) { return "{java11-javadoc}/java.base/" + fieldInfo.getDeclaring().replace('.', '/') + ".html#" + fieldInfo.getName(); } private static String getConstructorJavaDocLink(PainlessContextConstructorInfo constructorInfo) { StringBuilder javaDocLink = new StringBuilder(); javaDocLink.append("{java11-javadoc}/java.base/"); javaDocLink.append(constructorInfo.getDeclaring().replace('.', '/')); javaDocLink.append(".html#("); for (int parameterIndex = 0; parameterIndex < constructorInfo.getParameters().size(); ++parameterIndex) { javaDocLink.append(getLinkType(constructorInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < constructorInfo.getParameters().size()) { javaDocLink.append(","); } } javaDocLink.append(")"); return javaDocLink.toString(); } private static String getMethodJavaDocLink(PainlessContextMethodInfo methodInfo) { StringBuilder javaDocLink = new StringBuilder(); javaDocLink.append("{java11-javadoc}/java.base/"); javaDocLink.append(methodInfo.getDeclaring().replace('.', '/')); javaDocLink.append(".html#"); javaDocLink.append(methodInfo.getName()); javaDocLink.append("("); for (int parameterIndex = 0; parameterIndex < methodInfo.getParameters().size(); ++parameterIndex) { javaDocLink.append(getLinkType(methodInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < methodInfo.getParameters().size()) { javaDocLink.append(","); } } javaDocLink.append(")"); return javaDocLink.toString(); } private static String getLinkType(String javaType) { int arrayDimensions = 0; while (javaType.charAt(arrayDimensions) == '[') { ++arrayDimensions; } if (arrayDimensions > 0) { if (javaType.charAt(javaType.length() - 1) == ';') { javaType = javaType.substring(arrayDimensions + 1, javaType.length() - 1); } else { javaType = javaType.substring(arrayDimensions); } } if ("Z".equals(javaType) || "boolean".equals(javaType)) { javaType = "boolean"; } else if ("V".equals(javaType) || "void".equals(javaType)) { javaType = "void"; } else if ("B".equals(javaType) || "byte".equals(javaType)) { javaType = "byte"; } else if ("S".equals(javaType) || "short".equals(javaType)) { javaType = "short"; } else if ("C".equals(javaType) || "char".equals(javaType)) { javaType = "char"; } else if ("I".equals(javaType) || "int".equals(javaType)) { javaType = "int"; } else if ("J".equals(javaType) || "long".equals(javaType)) { javaType = "long"; } else if ("F".equals(javaType) || "float".equals(javaType)) { javaType = "float"; } else if ("D".equals(javaType) || "double".equals(javaType)) { javaType = "double"; } else if ("org.opensearch.painless.lookup.def".equals(javaType)) { javaType = "java.lang.Object"; } while (arrayDimensions-- > 0) { javaType += "%5B%5D"; } return javaType; } private static String getContextHeader(PainlessContextInfo contextInfo) { return "painless-api-reference-" + contextInfo.getName().replace(" ", "-").replace("_", "-"); } private static String getPackageHeader(String contextHeader, String packageName) { return contextHeader + "-" + packageName.replace('.', '-'); } private static String getClassHeader(String contextHeader, String className) { return contextHeader + "-" + className.replace('.', '-'); } private static String getContextName(PainlessContextInfo contextInfo) { String[] split = contextInfo.getName().split("[_-]"); StringBuilder contextNameBuilder = new StringBuilder(); for (String part : split) { contextNameBuilder.append(Character.toUpperCase(part.charAt(0))); contextNameBuilder.append(part.substring(1)); contextNameBuilder.append(' '); } return contextNameBuilder.substring(0, contextNameBuilder.length() - 1); } private static List sortStaticInfos(Set staticExcludes, List staticInfos) { staticInfos = new ArrayList<>(staticInfos); staticInfos.removeIf(staticExcludes::contains); staticInfos.sort((si1, si2) -> { String sv1; String sv2; if (si1 instanceof PainlessContextMethodInfo) { sv1 = ((PainlessContextMethodInfo) si1).getSortValue(); } else if (si1 instanceof PainlessContextClassBindingInfo) { sv1 = ((PainlessContextClassBindingInfo) si1).getSortValue(); } else if (si1 instanceof PainlessContextInstanceBindingInfo) { sv1 = ((PainlessContextInstanceBindingInfo) si1).getSortValue(); } else { throw new IllegalArgumentException("unexpected static info type"); } if (si2 instanceof PainlessContextMethodInfo) { sv2 = ((PainlessContextMethodInfo) si2).getSortValue(); } else if (si2 instanceof PainlessContextClassBindingInfo) { sv2 = ((PainlessContextClassBindingInfo) si2).getSortValue(); } else if (si2 instanceof PainlessContextInstanceBindingInfo) { sv2 = ((PainlessContextInstanceBindingInfo) si2).getSortValue(); } else { throw new IllegalArgumentException("unexpected static info type"); } return sv1.compareTo(sv2); }); return staticInfos; } private static List sortClassInfos( Set classExcludes, List classInfos ) { classInfos = new ArrayList<>(classInfos); classInfos.removeIf( v -> "void".equals(v.getName()) || "boolean".equals(v.getName()) || "byte".equals(v.getName()) || "short".equals(v.getName()) || "char".equals(v.getName()) || "int".equals(v.getName()) || "long".equals(v.getName()) || "float".equals(v.getName()) || "double".equals(v.getName()) || "org.opensearch.painless.lookup.def".equals(v.getName()) || isInternalClass(v.getName()) || classExcludes.contains(v) ); classInfos.sort((c1, c2) -> { String n1 = c1.getName(); String n2 = c2.getName(); boolean i1 = c1.isImported(); boolean i2 = c2.isImported(); String p1 = n1.substring(0, n1.lastIndexOf('.')); String p2 = n2.substring(0, n2.lastIndexOf('.')); int compare = p1.compareTo(p2); if (compare == 0) { if (i1 && i2) { compare = n1.substring(n1.lastIndexOf('.') + 1).compareTo(n2.substring(n2.lastIndexOf('.') + 1)); } else if (i1 == false && i2 == false) { compare = n1.compareTo(n2); } else { compare = Boolean.compare(i1, i2) * -1; } } return compare; }); return classInfos; } private static Map getDisplayNames(List classInfos) { Map javaNamesToDisplayNames = new HashMap<>(); for (PainlessContextClassInfo classInfo : classInfos) { String className = classInfo.getName(); if (classInfo.isImported()) { javaNamesToDisplayNames.put(className, className.substring(className.lastIndexOf('.') + 1).replace('$', '.')); } else { javaNamesToDisplayNames.put(className, className.replace('$', '.')); } } return javaNamesToDisplayNames; } private static boolean isInternalClass(String javaName) { return javaName.equals("org.opensearch.script.ScoreScript") || javaName.equals("org.opensearch.script.ScoreScript$ExplanationHolder"); } private ContextDocGenerator() { } }