From 80734c75b5f96fed862eb58c780bac861af74bee Mon Sep 17 00:00:00 2001 From: Robert Muir Date: Tue, 21 Jun 2016 08:35:12 -0400 Subject: [PATCH] get things started --- .../elasticsearch/painless/Augmentation.java | 126 ++++++++++++++++++ .../elasticsearch/painless/Definition.java | 126 ++++++++++++------ .../painless/node/LCallInvoke.java | 10 +- .../painless/node/LListShortcut.java | 12 +- .../painless/node/LMapShortcut.java | 12 +- .../painless/node/LShortcut.java | 12 +- .../elasticsearch/painless/node/SEach.java | 4 +- .../painless/node/SFunction.java | 2 +- .../org/elasticsearch/painless/java.lang.txt | 2 + .../painless/java.util.regex.txt | 2 +- .../org/elasticsearch/painless/java.util.txt | 3 +- .../painless/AugmentationTests.java | 35 +++++ 12 files changed, 262 insertions(+), 84 deletions(-) create mode 100644 modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java create mode 100644 modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java new file mode 100644 index 00000000000..456642eb8c5 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Augmentation.java @@ -0,0 +1,126 @@ +/* + * 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 java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.ObjIntConsumer; +import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.regex.Matcher; + +public class Augmentation { + public static int getLength(List receiver) { + return receiver.size(); + } + + public static String namedGroup(Matcher receiver, String name) { + return receiver.group(name); + } + + public static boolean any(Iterable receiver, Predicate predicate) { + for (T t : receiver) { + if (predicate.test(t)) { + return true; + } + } + return false; + } + + public static int count(Iterable receiver, Predicate predicate) { + int count = 0; + for (T t : receiver) { + if (predicate.test(t)) { + count++; + } + } + return count; + } + + public static Iterable each(Iterable receiver, Consumer consumer) { + receiver.forEach(consumer); + return receiver; + } + + public static Iterable eachWithIndex(Iterable receiver, ObjIntConsumer consumer) { + int count = 0; + for (T t : receiver) { + consumer.accept(t, count++); + } + return receiver; + } + + public static boolean every(Iterable receiver, Predicate predicate) { + for (T t : receiver) { + if (predicate.test(t) == false) { + return false; + } + } + return true; + } + + public static List findResults(Iterable receiver, Function filter) { + List list = new ArrayList<>(); + for (T t: receiver) { + U result = filter.apply(t); + if (result != null) { + list.add(result); + } + } + return list; + } + + public static Map> groupBy(Iterable receiver, Function mapper) { + Map> map = new LinkedHashMap<>(); + for (T t : receiver) { + U mapped = mapper.apply(t); + List results = map.get(mapped); + if (results == null) { + results = new ArrayList<>(); + map.put(mapped, results); + } + results.add(t); + } + return map; + } + + public static String join(Iterable receiver, String separator) { + StringBuilder sb = new StringBuilder(); + for (T t : receiver) { + if (sb.length() > 0) { + sb.append(separator); + } + sb.append(t); + } + return sb.toString(); + } + + public static double sum(Iterable receiver, ToDoubleFunction function) { + double sum = 0; + for (T t : receiver) { + sum += function.applyAsDouble(t); + } + return sum; + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java index 761a2afeeb1..376be44e28b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java @@ -186,15 +186,17 @@ public final class Definition { public static class Method { public final String name; public final Struct owner; + public final boolean augmentation; public final Type rtn; public final List arguments; public final org.objectweb.asm.commons.Method method; public final int modifiers; public final MethodHandle handle; - public Method(String name, Struct owner, Type rtn, List arguments, + public Method(String name, Struct owner, boolean augmentation, Type rtn, List arguments, org.objectweb.asm.commons.Method method, int modifiers, MethodHandle handle) { this.name = name; + this.augmentation = augmentation; this.owner = owner; this.rtn = rtn; this.arguments = Collections.unmodifiableList(arguments); @@ -217,7 +219,15 @@ public final class Definition { // otherwise compute it final Class params[]; final Class returnValue; - if (Modifier.isStatic(modifiers)) { + if (augmentation) { + // virtual/interface method disguised as static + params = new Class[1 + arguments.size()]; + params[0] = Augmentation.class; + for (int i = 0; i < arguments.size(); i++) { + params[i + 1] = arguments.get(i).clazz; + } + returnValue = rtn.clazz; + } else if (Modifier.isStatic(modifiers)) { // static method: straightforward copy params = new Class[arguments.size()]; for (int i = 0; i < arguments.size(); i++) { @@ -242,6 +252,24 @@ public final class Definition { } return MethodType.methodType(returnValue, params); } + + public void write(MethodWriter writer) { + final org.objectweb.asm.Type type; + if (augmentation) { + assert java.lang.reflect.Modifier.isStatic(modifiers); + type = org.objectweb.asm.Type.getType(Augmentation.class); + } else { + type = owner.type; + } + + if (java.lang.reflect.Modifier.isStatic(modifiers)) { + writer.invokeStatic(type, method); + } else if (java.lang.reflect.Modifier.isInterface(owner.clazz.getModifiers())) { + writer.invokeInterface(type, method); + } else { + writer.invokeVirtual(type, method); + } + } } public static final class Field { @@ -690,7 +718,7 @@ public final class Definition { " with arguments " + Arrays.toString(classes) + "."); } - final Method constructor = new Method(name, owner, returnType, Arrays.asList(args), asm, reflect.getModifiers(), handle); + final Method constructor = new Method(name, owner, false, returnType, Arrays.asList(args), asm, reflect.getModifiers(), handle); owner.constructors.put(methodKey, constructor); } @@ -734,24 +762,20 @@ public final class Definition { } addConstructorInternal(className, "", args); } else { - if (methodName.indexOf('/') >= 0) { - String nameAndAlias[] = methodName.split("/"); - if (nameAndAlias.length != 2) { - throw new IllegalArgumentException("Currently only two aliases are allowed!"); - } - addMethodInternal(className, nameAndAlias[0], nameAndAlias[1], rtn, args); + if (methodName.indexOf("*") >= 0) { + addMethodInternal(className, methodName.substring(0, methodName.length() - 1), true, rtn, args); } else { - addMethodInternal(className, methodName, null, rtn, args); + addMethodInternal(className, methodName, false, rtn, args); } } } else { // field - addFieldInternal(className, elements[1], null, rtn); + addFieldInternal(className, elements[1], rtn); } } - private final void addMethodInternal(final String struct, final String name, final String alias, - final Type rtn, final Type[] args) { + private final void addMethodInternal(String struct, String name, boolean augmentation, + Type rtn, Type[] args) { final Struct owner = structsMap.get(struct); if (owner == null) { @@ -777,20 +801,32 @@ public final class Definition { "Duplicate method signature [" + methodKey + "] found within the struct [" + owner.name + "]."); } - final Class[] classes = new Class[args.length]; - - for (int count = 0; count < classes.length; ++count) { - classes[count] = args[count].clazz; + final Class implClass; + final Class[] params; + + if (augmentation == false) { + implClass = owner.clazz; + params = new Class[args.length]; + for (int count = 0; count < args.length; ++count) { + params[count] = args[count].clazz; + } + } else { + implClass = Augmentation.class; + params = new Class[args.length + 1]; + params[0] = owner.clazz; + for (int count = 0; count < args.length; ++count) { + params[count+1] = args[count].clazz; + } } - + final java.lang.reflect.Method reflect; try { - reflect = owner.clazz.getMethod(alias == null ? name : alias, classes); - } catch (final NoSuchMethodException exception) { - throw new IllegalArgumentException("Method [" + (alias == null ? name : alias) + + reflect = implClass.getMethod(name, params); + } catch (NoSuchMethodException exception) { + throw new IllegalArgumentException("Method [" + name + "] not found for class [" + owner.clazz.getName() + "]" + - " with arguments " + Arrays.toString(classes) + "."); + " with arguments " + Arrays.toString(params) + "."); } if (!reflect.getReturnType().equals(rtn.clazz)) { @@ -805,25 +841,24 @@ public final class Definition { MethodHandle handle; try { - handle = MethodHandles.publicLookup().in(owner.clazz).unreflect(reflect); + handle = MethodHandles.publicLookup().in(implClass).unreflect(reflect); } catch (final IllegalAccessException exception) { - throw new IllegalArgumentException("Method [" + (alias == null ? name : alias) + "]" + - " not found for class [" + owner.clazz.getName() + "]" + - " with arguments " + Arrays.toString(classes) + "."); + throw new IllegalArgumentException("Method [" + name + "]" + + " not found for class [" + implClass.getName() + "]" + + " with arguments " + Arrays.toString(params) + "."); } final int modifiers = reflect.getModifiers(); - final Method method = new Method(name, owner, rtn, Arrays.asList(args), asm, modifiers, handle); + final Method method = new Method(name, owner, augmentation, rtn, Arrays.asList(args), asm, modifiers, handle); - if (java.lang.reflect.Modifier.isStatic(modifiers)) { + if (augmentation == false && java.lang.reflect.Modifier.isStatic(modifiers)) { owner.staticMethods.put(methodKey, method); } else { owner.methods.put(methodKey, method); } } - private final void addFieldInternal(final String struct, final String name, final String alias, - final Type type) { + private final void addFieldInternal(String struct, String name, Type type) { final Struct owner = structsMap.get(struct); if (owner == null) { @@ -844,9 +879,9 @@ public final class Definition { java.lang.reflect.Field reflect; try { - reflect = owner.clazz.getField(alias == null ? name : alias); + reflect = owner.clazz.getField(name); } catch (final NoSuchFieldException exception) { - throw new IllegalArgumentException("Field [" + (alias == null ? name : alias) + "]" + + throw new IllegalArgumentException("Field [" + name + "]" + " not found for class [" + owner.clazz.getName() + "]."); } @@ -862,7 +897,7 @@ public final class Definition { setter = MethodHandles.publicLookup().unreflectSetter(reflect); } } catch (final IllegalAccessException exception) { - throw new IllegalArgumentException("Getter/Setter [" + (alias == null ? name : alias) + "]" + + throw new IllegalArgumentException("Getter/Setter [" + name + "]" + " not found for class [" + owner.clazz.getName() + "]."); } @@ -875,9 +910,9 @@ public final class Definition { " within the struct [" + owner.name + "] is not final."); } - owner.staticMembers.put(alias == null ? name : alias, field); + owner.staticMembers.put(name, field); } else { - owner.members.put(alias == null ? name : alias, field); + owner.members.put(name, field); } } @@ -915,11 +950,24 @@ public final class Definition { // https://bugs.openjdk.java.net/browse/JDK-8072746 } else { try { - Class arguments[] = new Class[method.arguments.size()]; - for (int i = 0; i < method.arguments.size(); i++) { - arguments[i] = method.arguments.get(i).clazz; + // TODO: we *have* to remove all these public members and use getter methods to encapsulate! + final Class impl; + final Class arguments[]; + if (method.augmentation) { + impl = Augmentation.class; + arguments = new Class[method.arguments.size() + 1]; + arguments[0] = method.owner.clazz; + for (int i = 0; i < method.arguments.size(); i++) { + arguments[i + 1] = method.arguments.get(i).clazz; + } + } else { + impl = owner.clazz; + arguments = new Class[method.arguments.size()]; + for (int i = 0; i < method.arguments.size(); i++) { + arguments[i] = method.arguments.get(i).clazz; + } } - java.lang.reflect.Method m = owner.clazz.getMethod(method.method.getName(), arguments); + java.lang.reflect.Method m = impl.getMethod(method.method.getName(), arguments); if (m.getReturnType() != method.rtn.clazz) { throw new IllegalStateException("missing covariant override for: " + m + " in " + owner.name); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java index 21bfff65e8d..1056af2aaca 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCallInvoke.java @@ -122,14 +122,8 @@ public final class LCallInvoke extends ALink { for (AExpression argument : arguments) { argument.write(writer, globals); } - - if (java.lang.reflect.Modifier.isStatic(method.modifiers)) { - writer.invokeStatic(method.owner.type, method.method); - } else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) { - writer.invokeInterface(method.owner.type, method.method); - } else { - writer.invokeVirtual(method.owner.type, method.method); - } + + method.write(writer); } @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java index f2863ce3396..6ef8aedb0bf 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java @@ -92,11 +92,7 @@ final class LListShortcut extends ALink { void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { - writer.invokeInterface(getter.owner.type, getter.method); - } else { - writer.invokeVirtual(getter.owner.type, getter.method); - } + getter.write(writer); if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) { writer.checkCast(getter.rtn.type); @@ -107,11 +103,7 @@ final class LListShortcut extends ALink { void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { - writer.invokeInterface(setter.owner.type, setter.method); - } else { - writer.invokeVirtual(setter.owner.type, setter.method); - } + setter.write(writer); writer.writePop(setter.rtn.sort.size); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java index 3bc9ab57a37..52d66b0fe75 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java @@ -91,11 +91,7 @@ final class LMapShortcut extends ALink { void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { - writer.invokeInterface(getter.owner.type, getter.method); - } else { - writer.invokeVirtual(getter.owner.type, getter.method); - } + getter.write(writer); if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) { writer.checkCast(getter.rtn.type); @@ -106,11 +102,7 @@ final class LMapShortcut extends ALink { void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { - writer.invokeInterface(setter.owner.type, setter.method); - } else { - writer.invokeVirtual(setter.owner.type, setter.method); - } + setter.write(writer); writer.writePop(setter.rtn.sort.size); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java index 7f97b446382..6d669adc658 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java @@ -95,11 +95,7 @@ final class LShortcut extends ALink { void load(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { - writer.invokeInterface(getter.owner.type, getter.method); - } else { - writer.invokeVirtual(getter.owner.type, getter.method); - } + getter.write(writer); if (!getter.rtn.clazz.equals(getter.handle.type().returnType())) { writer.checkCast(getter.rtn.type); @@ -110,11 +106,7 @@ final class LShortcut extends ALink { void store(MethodWriter writer, Globals globals) { writer.writeDebugInfo(location); - if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { - writer.invokeInterface(setter.owner.type, setter.method); - } else { - writer.invokeVirtual(setter.owner.type, setter.method); - } + setter.write(writer); writer.writePop(setter.rtn.sort.size); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java index a90baac3203..772c4af4c48 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java @@ -206,10 +206,8 @@ public class SEach extends AStatement { Type itr = Definition.getType("Iterator"); org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(itr.type, Definition.DEF_TYPE.type); writer.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR); - } else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) { - writer.invokeInterface(method.owner.type, method.method); } else { - writer.invokeVirtual(method.owner.type, method.method); + method.write(writer); } writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index ba078d03dbd..46dc8af5ab9 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -114,7 +114,7 @@ public class SFunction extends AStatement { org.objectweb.asm.commons.Method method = new org.objectweb.asm.commons.Method(name, MethodType.methodType(rtnType.clazz, paramClasses).toMethodDescriptorString()); - this.method = new Method(name, null, rtnType, paramTypes, method, Modifier.STATIC | Modifier.PRIVATE, null); + this.method = new Method(name, null, false, rtnType, paramTypes, method, Modifier.STATIC | Modifier.PRIVATE, null); } @Override diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt index ec1a46e633f..859a7b314dd 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.lang.txt @@ -50,6 +50,8 @@ class Iterable -> java.lang.Iterable { void forEach(Consumer) Iterator iterator() Spliterator spliterator() + # some adaptations of groovy methods + boolean any*(Predicate) } # Readable: i/o diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt index 6befc865731..aaea78a7a96 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.regex.txt @@ -42,7 +42,7 @@ class Matcher -> java.util.regex.Matcher extends Object { boolean find(int) String group() String group(int) - String namedGroup/group(String) + String namedGroup*(String) int groupCount() boolean hasAnchoringBounds() boolean hasTransparentBounds() diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt index cccbf60f1a3..2a41b2c2c13 100644 --- a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/java.util.txt @@ -114,8 +114,7 @@ class List -> java.util.List extends Collection,Iterable { def remove(int) void replaceAll(UnaryOperator) def set(int,def) - # TODO: wtf? - int getLength/size() + int getLength*() void sort(Comparator) List subList(int,int) } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java new file mode 100644 index 00000000000..ccc3620b1c9 --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/AugmentationTests.java @@ -0,0 +1,35 @@ +/* + * 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; + +public class AugmentationTests extends ScriptTestCase { + + @AwaitsFix(bugUrl = "rmuir is working on this") + public void testCapturingReference() { + assertEquals(1, exec("int foo(Supplier t) { return t.get() }" + + "def l = new ArrayList(); l.add(1);" + + "return foo(l::getLength);")); + } + + + public void testIterable_Any() { + assertEquals(true, exec("List l = new ArrayList(); l.add(1); l.any(x -> x == 1)")); + } +}