
org.elasticsearch.painless.ScriptClassInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lang-painless Show documentation
Show all versions of lang-painless Show documentation
Elasticsearch module: lang-painless
The newest version!
/*
* 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.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.def;
import org.elasticsearch.painless.symbol.FunctionTable;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import static java.util.Collections.unmodifiableList;
import static org.elasticsearch.painless.WriterConstants.NEEDS_PARAMETER_METHOD_TYPE;
/**
* Information about the interface being implemented by the painless script.
*/
public class ScriptClassInfo {
private final Class> baseClass;
private final org.objectweb.asm.commons.Method executeMethod;
private final Class> executeMethodReturnType;
private final List executeArguments;
private final List needsMethods;
private final List getMethods;
private final List> getReturns;
public final List converters;
public final FunctionTable.LocalFunction defConverter;
public ScriptClassInfo(PainlessLookup painlessLookup, Class> baseClass) {
this.baseClass = baseClass;
// Find the main method and the uses$argName methods
java.lang.reflect.Method executeMethod = null;
List needsMethods = new ArrayList<>();
List getMethods = new ArrayList<>();
List> getReturns = new ArrayList<>();
Class> returnType = null;
for (java.lang.reflect.Method m : baseClass.getMethods()) {
if (m.isDefault()) {
continue;
}
if (m.getName().equals("execute")) {
if (executeMethod == null) {
executeMethod = m;
returnType = m.getReturnType();
} else {
throw new IllegalArgumentException(
"Painless can only implement interfaces that have a single method named [execute] but [" + baseClass.getName()
+ "] has more than one.");
}
} else if (m.getName().startsWith("needs") &&
m.getReturnType() == boolean.class &&
m.getParameterTypes().length == 0) {
needsMethods.add(new org.objectweb.asm.commons.Method(m.getName(), NEEDS_PARAMETER_METHOD_TYPE.toMethodDescriptorString()));
} else if (m.getName().startsWith("get") &&
m.getName().equals("getClass") == false &&
Modifier.isStatic(m.getModifiers()) == false) {
getReturns.add(
definitionTypeForClass(painlessLookup, m.getReturnType(), componentType -> "[" + m.getName() + "] has unknown return " +
"type [" + componentType.getName() + "]. Painless can only support getters with return types that are " +
"whitelisted."));
getMethods.add(new org.objectweb.asm.commons.Method(m.getName(),
MethodType.methodType(m.getReturnType()).toMethodDescriptorString()));
}
}
if (executeMethod == null) {
throw new IllegalStateException("no execute method found");
}
ArrayList converters = new ArrayList<>();
FunctionTable.LocalFunction defConverter = null;
for (java.lang.reflect.Method m : baseClass.getMethods()) {
if (m.getName().startsWith("convertFrom") &&
m.getParameterTypes().length == 1 &&
m.getReturnType() == returnType &&
Modifier.isStatic(m.getModifiers())) {
if (m.getName().equals("convertFromDef")) {
if (m.getParameterTypes()[0] != Object.class) {
throw new IllegalStateException("convertFromDef must take a single Object as an argument, " +
"not [" + m.getParameterTypes()[0] + "]");
}
defConverter = new FunctionTable.LocalFunction(m.getName(), m.getReturnType(), Arrays.asList(m.getParameterTypes()),
true, true);
} else {
converters.add(
new FunctionTable.LocalFunction(m.getName(), m.getReturnType(), Arrays.asList(m.getParameterTypes()), true, true)
);
}
}
}
this.defConverter = defConverter;
this.converters = unmodifiableList(converters);
MethodType methodType = MethodType.methodType(executeMethod.getReturnType(), executeMethod.getParameterTypes());
this.executeMethod = new org.objectweb.asm.commons.Method(executeMethod.getName(), methodType.toMethodDescriptorString());
executeMethodReturnType = definitionTypeForClass(painlessLookup, executeMethod.getReturnType(),
componentType -> "Painless can only implement execute methods returning a whitelisted type but [" + baseClass.getName()
+ "#execute] returns [" + componentType.getName() + "] which isn't whitelisted.");
// Look up the argument
List arguments = new ArrayList<>();
String[] argumentNamesConstant = readArgumentNamesConstant(baseClass);
Class>[] types = executeMethod.getParameterTypes();
if (argumentNamesConstant.length != types.length) {
throw new IllegalArgumentException("[" + baseClass.getName() + "#ARGUMENTS] has length [2] but ["
+ baseClass.getName() + "#execute] takes [1] argument.");
}
for (int arg = 0; arg < types.length; arg++) {
arguments.add(methodArgument(painlessLookup, types[arg], argumentNamesConstant[arg]));
}
this.executeArguments = unmodifiableList(arguments);
this.needsMethods = unmodifiableList(needsMethods);
this.getMethods = unmodifiableList(getMethods);
this.getReturns = unmodifiableList(getReturns);
}
/**
* The interface that the Painless script should implement.
*/
public Class> getBaseClass() {
return baseClass;
}
/**
* An asm method descriptor for the {@code execute} method.
*/
public org.objectweb.asm.commons.Method getExecuteMethod() {
return executeMethod;
}
/**
* The Painless {@link Class} or the return type of the {@code execute} method. This is used to generate the appropriate
* return bytecode.
*/
public Class> getExecuteMethodReturnType() {
return executeMethodReturnType;
}
/**
* Painless {@link Class}s and names of the arguments to the {@code execute} method. The names are exposed to the Painless
* script.
*/
public List getExecuteArguments() {
return executeArguments;
}
/**
* The {@code uses$varName} methods that must be implemented by Painless to complete implementing the interface.
*/
public List getNeedsMethods() {
return needsMethods;
}
/**
* The {@code getVarName} methods that must be implemented by Painless to complete implementing the interface.
*/
public List getGetMethods() {
return getMethods;
}
/**
* The {@code getVarName} methods return types.
*/
public List> getGetReturns() {
return getReturns;
}
/**
* Painless {@link Class}es and name of the argument to the {@code execute} method.
*/
public static class MethodArgument {
private final Class> clazz;
private final String name;
public MethodArgument(Class> clazz, String name) {
this.clazz = clazz;
this.name = name;
}
public Class> getClazz() {
return clazz;
}
public String getName() {
return name;
}
}
private MethodArgument methodArgument(PainlessLookup painlessLookup, Class> clazz, String argName) {
Class> defClass = definitionTypeForClass(painlessLookup, clazz, componentType -> "[" + argName + "] is of unknown type ["
+ componentType.getName() + ". Painless interfaces can only accept arguments that are of whitelisted types.");
return new MethodArgument(defClass, argName);
}
private static Class> definitionTypeForClass(PainlessLookup painlessLookup, Class> type,
Function, String> unknownErrorMessageSource) {
type = PainlessLookupUtility.javaTypeToType(type);
Class> componentType = type;
while (componentType.isArray()) {
componentType = componentType.getComponentType();
}
if (componentType != def.class && painlessLookup.lookupPainlessClass(componentType) == null) {
throw new IllegalArgumentException(unknownErrorMessageSource.apply(componentType));
}
return type;
}
private static String[] readArgumentNamesConstant(Class> iface) {
Field argumentNamesField;
try {
argumentNamesField = iface.getField("PARAMETERS");
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException("Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
+ "names of the method arguments but [" + iface.getName() + "] doesn't have one.", e);
}
if (false == argumentNamesField.getType().equals(String[].class)) {
throw new IllegalArgumentException("Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
+ "names of the method arguments but [" + iface.getName() + "] doesn't have one.");
}
try {
return (String[]) argumentNamesField.get(null);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalArgumentException("Error trying to read [" + iface.getName() + "#ARGUMENTS]", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy