You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2013/09/14 20:29:54 UTC

svn commit: r1523286 - in /lucene/dev/branches/lucene5207/lucene/expressions/src: java/org/apache/lucene/expressions/js/ resources/org/apache/lucene/expressions/js/

Author: rmuir
Date: Sat Sep 14 18:29:53 2013
New Revision: 1523286

URL: http://svn.apache.org/r1523286
Log:
LUCENE-5207: make functions pluggable

Added:
    lucene/dev/branches/lucene5207/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptCompiler.properties
      - copied unchanged from r1523280, lucene/dev/branches/lucene5207/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptFunction.properties
Removed:
    lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptFunction.java
    lucene/dev/branches/lucene5207/lucene/expressions/src/resources/org/apache/lucene/expressions/js/JavascriptFunction.properties
Modified:
    lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java

Modified: lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java?rev=1523286&r1=1523285&r2=1523286&view=diff
==============================================================================
--- lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java (original)
+++ lucene/dev/branches/lucene5207/lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java Sat Sep 14 18:29:53 2013
@@ -16,13 +16,19 @@ package org.apache.lucene.expressions.js
  * limitations under the License.
  */
 
+import java.io.IOException;
+import java.io.Reader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.text.ParseException;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 
 import org.antlr.runtime.ANTLRStringStream;
 import org.antlr.runtime.CharStream;
@@ -31,6 +37,7 @@ import org.antlr.runtime.RecognitionExce
 import org.antlr.runtime.tree.Tree;
 import org.apache.lucene.expressions.Expression;
 import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.util.IOUtils;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -144,6 +151,8 @@ public class JavascriptCompiler {
   private final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
   private MethodVisitor methodVisitor;
   
+  private final Map<String,Method> functions;
+  
   /**
    * Compiles the given expression.
    *
@@ -156,7 +165,24 @@ public class JavascriptCompiler {
   }
   
   /**
-   * This method is unused, it is just here to make sure that the funcion signatures don't change.
+   * Compiles the given expression with the supplied custom functions.
+   * <p>
+   * Functions must return a double.
+   *
+   * @param sourceText The expression to compile
+   * @param functions map of String names to functions
+   * @return A new compiled expression
+   * @throws ParseException on failure to compile
+   */
+  public static Expression compile(String sourceText, Map<String,Method> functions) throws ParseException {
+    for (Method m : functions.values()) {
+      checkFunction(m);
+    }
+    return new JavascriptCompiler(sourceText, functions).compileExpression();
+  }
+  
+  /**
+   * This method is unused, it is just here to make sure that the function signatures don't change.
    * If this method fails to compile, you also have to change the byte code generator to correctly
    * use the FunctionValues class.
    */
@@ -171,10 +197,19 @@ public class JavascriptCompiler {
    * @param sourceText The expression to compile
    */
   private JavascriptCompiler(String sourceText) {
+    this(sourceText, DEFAULT_FUNCTIONS);
+  }
+  
+  /**
+   * Constructs a compiler for expressions with specific set of functions
+   * @param sourceText The expression to compile
+   */
+  private JavascriptCompiler(String sourceText, Map<String,Method> functions) {
     if (sourceText == null) {
       throw new NullPointerException();
     }
     this.sourceText = sourceText;
+    this.functions = functions;
   }
   
   /**
@@ -230,13 +265,25 @@ public class JavascriptCompiler {
         String call = identifier.getText();
         int arguments = current.getChildCount() - 1;
         
-        JavascriptFunction method = JavascriptFunction.getMethod(call, arguments);
+        Method method = functions.get(call);
+        if (method == null) {
+          throw new IllegalArgumentException("Unrecognized method call (" + call + ").");
+        }
+        
+        int arity = method.getParameterTypes().length;
+        if (arguments != arity && arity != -1) {
+          throw new IllegalArgumentException("Expected (" + arity + ") arguments for method call (" +
+              call + "), but found (" + arguments + ").");
+        }
         
         for (int argument = 1; argument <= arguments; ++argument) {
           recursiveCompile(current.getChild(argument), ComputedType.DOUBLE);
         }
         
-        methodVisitor.visitMethodInsn(INVOKESTATIC, method.klass, method.method, method.descriptor);
+        String klass = Type.getInternalName(method.getDeclaringClass());
+        String name = method.getName();
+        String descriptor = Type.getMethodDescriptor(method);
+        methodVisitor.visitMethodInsn(INVOKESTATIC, klass, name, descriptor);
         
         typeCompile(expected, ComputedType.DOUBLE);
         break;
@@ -624,4 +671,49 @@ public class JavascriptCompiler {
       throw exception;
     }
   }
+  
+  /** 
+   * The default set of functions available to expressions.
+   * <p>
+   * See the {@link org.apache.lucene.expressions.js package documentation}
+   * for a list.
+   */
+  public static final Map<String,Method> DEFAULT_FUNCTIONS;
+  static {
+    Map<String,Method> map = new HashMap<String,Method>();
+    try {
+      final Properties props = new Properties();
+      try (Reader in = IOUtils.getDecodingReader(JavascriptCompiler.class,
+        JavascriptCompiler.class.getSimpleName() + ".properties", IOUtils.CHARSET_UTF_8)) {
+        props.load(in);
+      }
+      for (final String call : props.stringPropertyNames()) {
+        final String[] vals = props.getProperty(call).split(",");
+        if (vals.length != 3) {
+          throw new Error("Syntax error while reading Javascript functions from resource");
+        }
+        final Class<?> clazz = Class.forName(vals[0].trim());
+        final String methodName = vals[1].trim();
+        final int arity = Integer.parseInt(vals[2].trim());
+        @SuppressWarnings({"rawtypes", "unchecked"}) Class[] args = new Class[arity];
+        Arrays.fill(args, double.class);
+        Method method = clazz.getMethod(methodName, args);
+        checkFunction(method);
+        map.put(call, method);
+      }
+    } catch (NoSuchMethodException | ClassNotFoundException | IOException e) {
+      throw new Error("Cannot resolve function", e);
+    }
+    DEFAULT_FUNCTIONS = Collections.unmodifiableMap(map);
+  }
+  
+  /* do some checks if the signature is "compatible" */
+  private static void checkFunction(Method method) {
+    if (!Modifier.isStatic(method.getModifiers())) {
+      throw new IllegalArgumentException(method + " is not static.");
+    }
+    if (method.getReturnType() != double.class) {
+      throw new IllegalArgumentException(method + " does not return a double.");
+    }
+  }
 }