1

UPDATE: After getting an unexpected-in-a-good-way answer, I've added some context to the bottom of this question, stating exactly how I'll be using these string-function-calls.

I need to translate a string such as

my.package.ClassName#functionName(1, "a string value", true)

into a reflective call to that function. Getting the package, class, and function name is not a problem. I have started rolling my own solution for parsing the parameter list, and determining the type of each and returning an appropriate object.

(I'm limiting the universe of types to the eight primitives, plus string. null would be considered a string, and commas and double-quotes must be strictly escaped with some simple marker, such as __DBL_QT__, to avoid complications with unescaping and splitting on the comma.)

I am not asking how to do this via string-parsing, as I understand how. It's just a lot of work and I'm hoping there's a solution already out there. Unfortunately it's such generic terminology, I'm getting nowhere with searching.

I understand asking for an external existing library is off topic for SO. I'm just hoping to get some feedback before it's shutdown, or even a suggestion on better search terms. Or perhaps, there is a completely different approach that might be suggested...

Thank you.


Context:

Each function call is found within a function's JavaDoc block, and represents a piece of example code--either its source code or its System.out output--which will be displayed in that spot.

The parameters are for customizing its display, such as

  • indentation,
  • eliminating irrelevant parts (like the license-block), and
  • for JavaDoc-linking the most important functions. This customization is mostly for the source-code presentation, but may also be applied to its output.

(The first parameter is always an Appendable, which will do the actual outputting.)

The user needs to be be able to call any function, which in many cases will be a private-static function located directly below the JavaDoc-ed function itself.

The application I'm writing will read in the source-code file (the one containing the JavaDoc blocks, in which these string-function-calls exist), and create a duplicate of the *.java file, which will subsequently processed by javadoc.

So for every piece of example code, there will be likely two, and possibly more of these string-function-calls. There may be more, because I may want to show different slices of the same example, in different contexts--perhaps the whole example in the overall class JavaDoc block, and snippets from it in the relevant functions in that class.

I have already written the process that parses the source code (the source code containing the JavaDoc blocks, which is separate from the one that reads the example-code), and re-outputs its source-code blindly with insert example-code here and insert example-code-output here markers.

I'm now at the point where I have this string-function-call in an InsertExampleCode object, in a string-field. Now I need to do as described at the top of this question. Figure out which function they want to invoke, and do so.

aliteralmind
  • 19,847
  • 17
  • 77
  • 108

2 Answers2

1

Change the # to a dot (.), write a class definition around it so that you have a valid Java source file, include tools.jar in your classpath and invoke com.sun.tools.javac.Main.

Create your own instance of a ClassLoader to load the compiled class, and run it (make it implement a useful interface, such as java.util.concurrent.Callable so that you can get the result of the invocation easily)

That should do the trick.

Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • These are things I've never done, but it's an impressive idea which makes my whole approach moot. There's no API for `javac`, which I expected, and searching for it brought up [this question](http://stackoverflow.com/questions/10314904/no-com-sun-tools-javac-in-jdk7), in which the second answer refers to [javax.tools.JavaCompiler](http://docs.oracle.com/javase/6/docs/api/javax/tools/JavaCompiler.html). If you are familiar, could you comment on which might be a better approach, invoking `javac` directly, or going through `JavaCompiler`? – aliteralmind Mar 25 '14 at 14:52
  • Two more questions: In my project, this function will always return void. Is there any advantage over loading the compiled-class, as opposed to simply invoking it, since the call would be inside its `main` function? There are potentially hundreds of these types of function calls within a single Ant build process. Does calling `javac`/`JavaCompiler` at this frequency have disadvantages? – aliteralmind Mar 25 '14 at 15:07
  • For future users, this question is also useful: http://stackoverflow.com/questions/12173294/compiling-fully-in-memory-with-javax-tools-javacompiler – aliteralmind Mar 25 '14 at 15:07
  • I'm a bit old-school, used to invoke above class directly before `JavaCompiler` existed. I see the Oracle docs on `JavaCompiler` are quite good with an example: http://docs.oracle.com/javase/7/docs/api/javax/tools/JavaCompiler.html – Erwin Bolwidt Mar 25 '14 at 15:09
  • I would invoke JavaCompiler directly from Java, not go through external program invocation. That only makes things slower, brittle, and it seems that using JavaCompiler and `SimpleJavaFileObject` make it possible to compile the source from memory without an intermediate file which is also less brittle. – Erwin Bolwidt Mar 25 '14 at 15:10
  • And yes, if you have hundreds of function calls, the Java Compiler will slow things down. Maybe it's better to use a scripting engine instead with a syntax close to Java, such as Groovy. But I guess you would need to do a performance test with real-life data to be certain about the speed. – Erwin Bolwidt Mar 25 '14 at 15:13
  • Do you have an opinion on the best approach here? Scripting/Groovy, or string-parsing the parameters and invoking via reflection? I'm barely familiar with Groovy. About to look it up. – aliteralmind Mar 25 '14 at 15:18
  • I've added context to my question, to show how I'll be using these string-function-calls. – aliteralmind Mar 25 '14 at 15:49
  • 1
    If you want to execute the expression often, I'd go for the java compiler. If you want to execute many expressions once, I'd go for Groovy. – Erwin Bolwidt Mar 25 '14 at 15:50
  • I appreciate your answer and comments. Thank you. I also find it interesting that a guy named Erwin is in Singapore :) – aliteralmind Mar 25 '14 at 16:40
0

The class I created for this, called com.github.aliteralmind.codelet.simplesig.SimpleMethodSignature, is a significant piece of Codelet, used to translate the "customizer" portion of each taglet, which is a function that customizes the taglet's output.

(Installation instructions. The only jars that must be in your classpath are codelet and xbnjava.)

Example string signatures, in taglets:

{@.codelet.and.out com.github.aliteralmind.codelet.examples.adder.AdderDemo%eliminateCommentBlocksAndPackageDecl()}

The customizer portion is everything following the percent sign (%). This customizer contains only the function name and empty parameters. This implies that the function must exist in one of a few, strictly-specified, set of classes.

{@.codelet.and.out com.github.aliteralmind.codelet.examples.adder.AdderDemo%lineRange(1, false, "Adder adder", 2, false, "println(adder.getSum())", "^ ")}

This specifies parameters as well, which are, by design, "simple"--either non-null strings, or a primitive type.

{@.codelet.and.out com.github.aliteralmind.codelet.examples.adder.AdderDemo%com.github.aliteralmind.codelet.examples.LineRangeWithLinksCompact#adderDemo_lineSnippetWithLinks()}

Specifies the explicit package and class in which the function exists.


Because of the nature of these taglets and how the string-signatures are implemented, I decided to stick with direct string parsing instead of dynamic compilation.

Two example uses of SimpleMethodSignature:

In this first example, the full signature (the package, class, and function name, including all its parameters) are specified in the string.

   import  com.github.aliteralmind.codelet.simplesig.SimpleMethodSignature;
   import  com.github.xbn.lang.reflect.InvokeMethodWithRtx;
   import  java.lang.reflect.Method;
public class SimpleMethodSigNoDefaults  {

   public static final void main(String[] ignored)  {

      String strSig = "com.github.aliteralmind.codelet.examples.simplesig." +
         "SimpleMethodSigNoDefaults#getStringForBoolInt(false, 3)";

      SimpleMethodSignature simpleSig = null;
      try  {
         simpleSig = SimpleMethodSignature.newFromStringAndDefaults(
            String.class, strSig, null, null,
            null);         //debug (on=System.out, off=null)
      }  catch(ClassNotFoundException cnfx)  {
         throw  new RuntimeException(cnfx);
      }

      Method m = null;
      try  {
         m = simpleSig.getMethod();
      }  catch(NoSuchMethodException nsmx)  {
         throw  new RuntimeException(nsmx);
      }

      m.setAccessible(true);

      Object returnValue = new InvokeMethodWithRtx(m).sstatic().
         parameters(simpleSig.getParamValueObjectList().toArray()).invokeGetReturnValue();

      System.out.println(returnValue);
   }
   public static final String getStringForBoolInt(Boolean b, Integer i)  {
      return  "b=" + b + ", i=" + i;
   }
}

Output:

b=false, i=3

This second example demonstrates a string signature in which the (package and) class name are not specified. The potential classes, one in which the function must exist, are provided directly.

   import  com.github.aliteralmind.codelet.simplesig.SimpleMethodSignature;
   import  com.github.xbn.lang.reflect.InvokeMethodWithRtx;
   import  java.lang.reflect.Method;
public class SimpleMethodSigWithClassDefaults  {
   public static final void main(String[] ignored)  {

      String strSig = "getStringForBoolInt(false, 3)";

      SimpleMethodSignature simpleSig = null;
      try  {
         simpleSig = SimpleMethodSignature.newFromStringAndDefaults(
            String.class, strSig, null,
            new Class[]{Object.class, SimpleMethodSigWithClassDefaults.class, SimpleMethodSignature.class},
            null);         //debug (on=System.out, off=null)
      }  catch(ClassNotFoundException cnfx)  {
         throw  new RuntimeException(cnfx);
      }

      Method m = null;
      try  {
         m = simpleSig.getMethod();
      }  catch(NoSuchMethodException nsmx)  {
         throw  new RuntimeException(nsmx);
      }

      m.setAccessible(true);

      Object returnValue = new InvokeMethodWithRtx(m).sstatic().
         parameters(simpleSig.getParamValueObjectList().toArray()).invokeGetReturnValue();

      System.out.println(returnValue);
   }
   public static final String getStringForBoolInt(Boolean b, Integer i)  {
      return  "b=" + b + ", i=" + i;
   }
}

Output:

b=false, i=3
aliteralmind
  • 19,847
  • 17
  • 77
  • 108