diff --git a/modules/lang-painless/build.gradle b/modules/lang-painless/build.gradle index 12d18b84f7733..bd034ff97ddb7 100644 --- a/modules/lang-painless/build.gradle +++ b/modules/lang-painless/build.gradle @@ -106,6 +106,26 @@ tasks.register("generateContextDoc", DefaultTestClustersTask) { }.assertNormalExitValue() } } +/********************************************** + * Context JSON Generation * + **********************************************/ +testClusters { + generateContextApiSpecCluster { + testDistribution = 'DEFAULT' + } +} + +tasks.register("generateContextApiSpec", DefaultTestClustersTask) { + dependsOn sourceSets.doc.runtimeClasspath + useCluster testClusters.generateContextApiSpecCluster + doFirst { + project.javaexec { + main = 'org.elasticsearch.painless.ContextApiSpecGenerator' + classpath = sourceSets.doc.runtimeClasspath + systemProperty "cluster.uri", "${-> testClusters.generateContextApiSpecCluster.singleNode().getAllHttpSocketURI().get(0)}" + }.assertNormalExitValue() + } +} /********************************************** * Parser regeneration * diff --git a/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextApiSpecGenerator.java b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextApiSpecGenerator.java new file mode 100644 index 0000000000000..9b0f4f3337fac --- /dev/null +++ b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextApiSpecGenerator.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.io.PathUtils; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.core.internal.io.IOUtils; +import org.elasticsearch.painless.action.PainlessContextInfo; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.List; + +public class ContextApiSpecGenerator { + public static void main(String[] args) throws IOException { + List contexts = ContextGeneratorCommon.getContextInfos(); + ContextGeneratorCommon.PainlessInfos infos = new ContextGeneratorCommon.PainlessInfos(contexts); + Path rootDir = resetRootDir(); + Path json = rootDir.resolve("painless-common.json"); + try (PrintStream jsonStream = new PrintStream( + Files.newOutputStream(json, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), + false, StandardCharsets.UTF_8.name())) { + + XContentBuilder builder = XContentFactory.jsonBuilder(jsonStream); + builder.startObject(); + builder.field(PainlessContextInfo.CLASSES.getPreferredName(), infos.common); + builder.endObject(); + builder.flush(); + } + + for (PainlessInfoJson.Context context : infos.contexts) { + json = rootDir.resolve("painless-" + context.getName() + ".json"); + try (PrintStream jsonStream = new PrintStream( + Files.newOutputStream(json, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), + false, StandardCharsets.UTF_8.name())) { + + XContentBuilder builder = XContentFactory.jsonBuilder(jsonStream); + context.toXContent(builder, null); + builder.flush(); + } + } + } + + @SuppressForbidden(reason = "resolve context api directory with environment") + private static Path resetRootDir() throws IOException { + Path rootDir = PathUtils.get("./src/main/generated/whitelist-json"); + IOUtils.rm(rootDir); + Files.createDirectories(rootDir); + + return rootDir; + } +} diff --git a/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextDocGenerator.java b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextDocGenerator.java index d110748164555..9ee31222bd2a8 100644 --- a/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextDocGenerator.java +++ b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextDocGenerator.java @@ -21,8 +21,6 @@ import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.io.PathUtils; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.painless.action.PainlessContextClassBindingInfo; import org.elasticsearch.painless.action.PainlessContextClassInfo; @@ -34,16 +32,12 @@ 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; @@ -70,7 +64,7 @@ public final class ContextDocGenerator { private static final String SHARED_NAME = "Shared"; public static void main(String[] args) throws IOException { - List contextInfos = getContextInfos(); + List contextInfos = ContextGeneratorCommon.getContextInfos(); Set sharedStaticInfos = createSharedStatics(contextInfos); Set sharedClassInfos = createSharedClasses(contextInfos); @@ -102,33 +96,6 @@ public static void main(String[] args) throws IOException { 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<>(); @@ -291,7 +258,7 @@ private static void printIndex(PrintStream indexStream, String contextHeader, Ma indexStream.println(); } - String className = getType(javaNamesToDisplayNames, classInfo.getName()); + String className = ContextGeneratorCommon.getType(javaNamesToDisplayNames, classInfo.getName()); indexStream.println("* <<" + getClassHeader(contextHeader, className) + ", " + className + ">>"); } } @@ -354,7 +321,7 @@ private static void printPackages(PrintStream packagesStream, String contextName "for a high-level overview of all packages and classes."); } - String className = getType(javaNamesToDisplayNames, classInfo.getName()); + String className = ContextGeneratorCommon.getType(javaNamesToDisplayNames, classInfo.getName()); packagesStream.println(); packagesStream.println("[[" + getClassHeader(contextHeader, className) + "]]"); packagesStream.println("==== " + className + ""); @@ -443,7 +410,7 @@ private static void printConstructor( parameterIndex < constructorInfo.getParameters().size(); ++parameterIndex) { - stream.print(getType(javaNamesToDisplayNames, constructorInfo.getParameters().get(parameterIndex))); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, constructorInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < constructorInfo.getParameters().size()) { stream.print(", "); @@ -458,7 +425,7 @@ private static void printMethod( boolean isStatic, PainlessContextMethodInfo methodInfo) { stream.print("* " + (isStatic ? "static " : "")); - stream.print(getType(javaNamesToDisplayNames, methodInfo.getRtn()) + " "); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, methodInfo.getRtn()) + " "); if (methodInfo.getDeclaring().startsWith("java.")) { stream.print(getMethodJavaDocLink(methodInfo) + "[" + methodInfo.getName() + "]"); @@ -472,7 +439,7 @@ private static void printMethod( parameterIndex < methodInfo.getParameters().size(); ++parameterIndex) { - stream.print(getType(javaNamesToDisplayNames, methodInfo.getParameters().get(parameterIndex))); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, methodInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < methodInfo.getParameters().size()) { stream.print(", "); @@ -485,17 +452,21 @@ private static void printMethod( private static void printClassBinding( PrintStream stream, Map javaNamesToDisplayNames, PainlessContextClassBindingInfo classBindingInfo) { - stream.print("* " + getType(javaNamesToDisplayNames, classBindingInfo.getRtn()) + " " + classBindingInfo.getName() + "("); + stream.print("* " + + ContextGeneratorCommon.getType(javaNamesToDisplayNames, classBindingInfo.getRtn()) + + " " + + classBindingInfo.getName() + + "("); for (int parameterIndex = 0; parameterIndex < classBindingInfo.getParameters().size(); ++parameterIndex) { // temporary fix to not print org.elasticsearch.script.ScoreScript parameter until // class instance bindings are created and the information is appropriately added to the context info classes if ("org.elasticsearch.script.ScoreScript".equals( - getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex)))) { + ContextGeneratorCommon.getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex)))) { continue; } - stream.print(getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex))); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, classBindingInfo.getParameters().get(parameterIndex))); if (parameterIndex < classBindingInfo.getReadOnly()) { stream.print(" *"); @@ -512,10 +483,14 @@ private static void printClassBinding( private static void printInstanceBinding( PrintStream stream, Map javaNamesToDisplayNames, PainlessContextInstanceBindingInfo instanceBindingInfo) { - stream.print("* " + getType(javaNamesToDisplayNames, instanceBindingInfo.getRtn()) + " " + instanceBindingInfo.getName() + "("); + stream.print("* " + + ContextGeneratorCommon.getType(javaNamesToDisplayNames, instanceBindingInfo.getRtn()) + + " " + + instanceBindingInfo.getName() + + "("); for (int parameterIndex = 0; parameterIndex < instanceBindingInfo.getParameters().size(); ++parameterIndex) { - stream.print(getType(javaNamesToDisplayNames, instanceBindingInfo.getParameters().get(parameterIndex))); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, instanceBindingInfo.getParameters().get(parameterIndex))); if (parameterIndex + 1 < instanceBindingInfo.getParameters().size()) { stream.print(", "); @@ -530,7 +505,7 @@ private static void printField( boolean isStatic, PainlessContextFieldInfo fieldInfo) { stream.print("* " + (isStatic ? "static " : "")); - stream.print(getType(javaNamesToDisplayNames, fieldInfo.getType()) + " "); + stream.print(ContextGeneratorCommon.getType(javaNamesToDisplayNames, fieldInfo.getType()) + " "); if (fieldInfo.getDeclaring().startsWith("java.")) { stream.println(getFieldJavaDocLink(fieldInfo) + "[" + fieldInfo.getName() + "]"); @@ -539,52 +514,6 @@ private static void printField( } } - 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.elasticsearch.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(); } diff --git a/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextGeneratorCommon.java b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextGeneratorCommon.java new file mode 100644 index 0000000000000..bab8907b557e9 --- /dev/null +++ b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/ContextGeneratorCommon.java @@ -0,0 +1,236 @@ +/* + * 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. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.painless.action.PainlessContextClassBindingInfo; +import org.elasticsearch.painless.action.PainlessContextClassInfo; +import org.elasticsearch.painless.action.PainlessContextInfo; +import org.elasticsearch.painless.action.PainlessContextInstanceBindingInfo; +import org.elasticsearch.painless.action.PainlessContextMethodInfo; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ContextGeneratorCommon { + @SuppressForbidden(reason = "retrieving data from an internal API not exposed as part of the REST client") + public 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; + } + + public 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.elasticsearch.painless.lookup.def".equals(javaType)) { + javaType = "def"; + } else { + javaType = javaNamesToDisplayNames.get(javaType); + } + + while (arrayDimensions-- > 0) { + javaType += "[]"; + } + + return javaType; + } + + private static Map getDisplayNames(Collection contextInfos) { + Map javaNamesToDisplayNames = new HashMap<>(); + + for (PainlessContextInfo contextInfo : contextInfos) { + for (PainlessContextClassInfo classInfo : contextInfo.getClasses()) { + String className = classInfo.getName(); + if (javaNamesToDisplayNames.containsKey(className) == false) { + if (classInfo.isImported()) { + javaNamesToDisplayNames.put(className, + className.substring(className.lastIndexOf('.') + 1).replace('$', '.')); + } else { + javaNamesToDisplayNames.put(className, className.replace('$', '.')); + } + } + } + } + return javaNamesToDisplayNames; + } + + public static List sortClassInfos(Collection unsortedClassInfos) { + + List classInfos = new ArrayList<>(unsortedClassInfos); + 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.elasticsearch.painless.lookup.def".equals(v.getName()) || + isInternalClass(v.getName()) + ); + + 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 boolean isInternalClass(String javaName) { + return javaName.equals("org.elasticsearch.script.ScoreScript") || + javaName.equals("org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoShape") || + javaName.equals("org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalSqlScriptUtils") || + javaName.equals("org.elasticsearch.xpack.sql.expression.literal.IntervalDayTime") || + javaName.equals("org.elasticsearch.xpack.sql.expression.literal.IntervalYearMonth") || + javaName.equals("org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalEqlScriptUtils") || + javaName.equals("org.elasticsearch.xpack.ql.expression.function.scalar.InternalQlScriptUtils") || + javaName.equals("org.elasticsearch.xpack.ql.expression.function.scalar.whitelist.InternalQlScriptUtils") || + javaName.equals("org.elasticsearch.script.ScoreScript$ExplanationHolder"); + } + + public static List excludeCommonClassInfos( + Set exclude, + List classInfos + ) { + List uniqueClassInfos = new ArrayList<>(classInfos); + uniqueClassInfos.removeIf(exclude::contains); + return uniqueClassInfos; + } + + public static class PainlessInfos { + public final Set importedMethods; + public final Set classBindings; + public final Set instanceBindings; + + public final List common; + public final List contexts; + + public final Map javaNamesToDisplayNames; + + public PainlessInfos(List contextInfos) { + javaNamesToDisplayNames = getDisplayNames(contextInfos); + + Set commonClassInfos = getCommon(contextInfos, PainlessContextInfo::getClasses); + common = PainlessInfoJson.Class.fromInfos(sortClassInfos(commonClassInfos), javaNamesToDisplayNames); + + importedMethods = getCommon(contextInfos, PainlessContextInfo::getImportedMethods); + + classBindings = getCommon(contextInfos, PainlessContextInfo::getClassBindings); + + instanceBindings = getCommon(contextInfos, PainlessContextInfo::getInstanceBindings); + + contexts = contextInfos.stream() + .map(ctx -> new PainlessInfoJson.Context(ctx, commonClassInfos, javaNamesToDisplayNames)) + .collect(Collectors.toList()); + } + + private Set getCommon(List contexts, Function> getter) { + Map infoCounts = new HashMap<>(); + for (PainlessContextInfo contextInfo : contexts) { + for (T info : getter.apply(contextInfo)) { + infoCounts.merge(info, 1, Integer::sum); + } + } + return infoCounts.entrySet().stream().filter( + e -> e.getValue() == contexts.size() + ).map(Map.Entry::getKey).collect(Collectors.toSet()); + } + } +} diff --git a/modules/lang-painless/src/doc/java/org/elasticsearch/painless/PainlessInfoJson.java b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/PainlessInfoJson.java new file mode 100644 index 0000000000000..7b55a0013874d --- /dev/null +++ b/modules/lang-painless/src/doc/java/org/elasticsearch/painless/PainlessInfoJson.java @@ -0,0 +1,209 @@ +/* + * 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. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.painless.action.PainlessContextClassBindingInfo; +import org.elasticsearch.painless.action.PainlessContextClassInfo; +import org.elasticsearch.painless.action.PainlessContextConstructorInfo; +import org.elasticsearch.painless.action.PainlessContextFieldInfo; +import org.elasticsearch.painless.action.PainlessContextInfo; +import org.elasticsearch.painless.action.PainlessContextInstanceBindingInfo; +import org.elasticsearch.painless.action.PainlessContextMethodInfo; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class PainlessInfoJson { + public static class Context implements ToXContentObject { + private final String name; + private final List classes; + private final List importedMethods; + private final List classBindings; + private final List instanceBindings; + + public Context( + PainlessContextInfo info, + Set commonClassInfos, + Map javaNamesToDisplayNames + ) { + this.name = info.getName(); + List classInfos = ContextGeneratorCommon.excludeCommonClassInfos(commonClassInfos, info.getClasses()); + classInfos = ContextGeneratorCommon.sortClassInfos(classInfos); + this.classes = Class.fromInfos(classInfos, javaNamesToDisplayNames); + this.importedMethods = Method.fromInfos(info.getImportedMethods(), javaNamesToDisplayNames); + this.classBindings = info.getClassBindings(); + this.instanceBindings = info.getInstanceBindings(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PainlessContextInfo.NAME.getPreferredName(), name); + builder.field(PainlessContextInfo.CLASSES.getPreferredName(), classes); + builder.field(PainlessContextInfo.IMPORTED_METHODS.getPreferredName(), importedMethods); + builder.field(PainlessContextInfo.CLASS_BINDINGS.getPreferredName(), classBindings); + builder.field(PainlessContextInfo.INSTANCE_BINDINGS.getPreferredName(), instanceBindings); + builder.endObject(); + + return builder; + } + + public String getName() { + return name; + } + } + + public static class Class implements ToXContentObject { + private final String name; + private final boolean imported; + private final List constructors; + private final List staticMethods; + private final List methods; + private final List staticFields; + private final List fields; + + public Class(PainlessContextClassInfo info, Map javaNamesToDisplayNames) { + this.name = javaNamesToDisplayNames.get(info.getName()); + this.imported = info.isImported(); + this.constructors = Constructor.fromInfos(info.getConstructors(), javaNamesToDisplayNames); + this.staticMethods = Method.fromInfos(info.getStaticMethods(), javaNamesToDisplayNames); + this.methods = Method.fromInfos(info.getMethods(), javaNamesToDisplayNames); + this.staticFields = Field.fromInfos(info.getStaticFields(), javaNamesToDisplayNames); + this.fields = Field.fromInfos(info.getFields(), javaNamesToDisplayNames); + } + + public static List fromInfos(List infos, Map javaNamesToDisplayNames) { + return infos.stream() + .map(info -> new Class(info, javaNamesToDisplayNames)) + .collect(Collectors.toList()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PainlessContextClassInfo.NAME.getPreferredName(), name); + builder.field(PainlessContextClassInfo.IMPORTED.getPreferredName(), imported); + builder.field(PainlessContextClassInfo.CONSTRUCTORS.getPreferredName(), constructors); + builder.field(PainlessContextClassInfo.STATIC_METHODS.getPreferredName(), staticMethods); + builder.field(PainlessContextClassInfo.METHODS.getPreferredName(), methods); + builder.field(PainlessContextClassInfo.STATIC_FIELDS.getPreferredName(), staticFields); + builder.field(PainlessContextClassInfo.FIELDS.getPreferredName(), fields); + builder.endObject(); + + return builder; + } + } + + public static class Method implements ToXContentObject { + private final String declaring; + private final String name; + private final String rtn; + private final List parameters; + + public Method(PainlessContextMethodInfo info, Map javaNamesToDisplayNames) { + this.declaring = javaNamesToDisplayNames.get(info.getDeclaring()); + this.name = info.getName(); + this.rtn = ContextGeneratorCommon.getType(javaNamesToDisplayNames, info.getRtn()); + this.parameters = info.getParameters().stream() + .map(p -> ContextGeneratorCommon.getType(javaNamesToDisplayNames, p)) + .collect(Collectors.toList()); + } + + public static List fromInfos(List infos, Map javaNamesToDisplayNames) { + return infos.stream() + .map(m -> new Method(m, javaNamesToDisplayNames)) + .collect(Collectors.toList()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PainlessContextMethodInfo.DECLARING.getPreferredName(), declaring); + builder.field(PainlessContextMethodInfo.NAME.getPreferredName(), name); + builder.field(PainlessContextMethodInfo.RTN.getPreferredName(), rtn); + builder.field(PainlessContextMethodInfo.PARAMETERS.getPreferredName(), parameters); + builder.endObject(); + + return builder; + } + } + + public static class Constructor implements ToXContentObject { + private final String declaring; + private final List parameters; + + public Constructor(PainlessContextConstructorInfo info, Map javaNamesToDisplayNames) { + this.declaring = javaNamesToDisplayNames.get(info.getDeclaring()); + this.parameters = info.getParameters().stream() + .map(p -> ContextGeneratorCommon.getType(javaNamesToDisplayNames, p)) + .collect(Collectors.toList()); + } + + public static List fromInfos(List infos, Map javaNamesToDisplayNames) { + return infos.stream() + .map(c -> new Constructor(c, javaNamesToDisplayNames)) + .collect(Collectors.toList()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PainlessContextConstructorInfo.DECLARING.getPreferredName(), declaring); + builder.field(PainlessContextConstructorInfo.PARAMETERS.getPreferredName(), parameters); + builder.endObject(); + + return builder; + } + } + + public static class Field implements ToXContentObject { + private final String declaring; + private final String name; + private final String type; + + public Field(PainlessContextFieldInfo info, Map javaNamesToDisplayNames) { + this.declaring = javaNamesToDisplayNames.get(info.getDeclaring()); + this.name = info.getName(); + this.type = ContextGeneratorCommon.getType(javaNamesToDisplayNames, info.getType()); + } + + public static List fromInfos(List infos, Map javaNamesToDisplayNames) { + return infos.stream() + .map(f -> new Field(f, javaNamesToDisplayNames)) + .collect(Collectors.toList()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(PainlessContextFieldInfo.DECLARING.getPreferredName(), declaring); + builder.field(PainlessContextFieldInfo.NAME.getPreferredName(), name); + builder.field(PainlessContextFieldInfo.TYPE.getPreferredName(), type); + builder.endObject(); + + return builder; + } + } +}