Translating a string-representation of a function&

2019-08-13 18:13发布

问题:

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.

回答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.



回答2:

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