3

i have a populated arraylist (non-codemodel) in a code generation class using codemodel, and i would like to use it in the generated code. is there any way to do this? it's "crossing worlds" a little, as the generated code does not reference or depend on the code generating it, however i'd still like to use the value of the arraylist without having to go through manually copying it in a traversal of value by value... there has to be a way to do this as there is with strings, numbers, etc...?

my example code is as follows:

private JMethod makeHeadersWrapper( String endPointName, ArrayList<BasicNameValuePair> headersList )
{

    JMethod wrapperMethod = definedClass.method( JMod.PUBLIC | JMod.STATIC, codeModel.ref( headersList.getClass() ).narrow( BasicNameValuePair.class ), StringUtils.formatName( "make" + endPointName + "Header", false, StringUtils.FormatType.UP_ENGLISH_WORDS ) );

    JVar headersListVar = wrapperMethod.body().decl( wrapperMethod.type(), "headersList", {X} ) );
    wrapperMethod.body()._return( headersListVar );

    return wrapperMethod;

}

as you see in my declaration of the assignment there is an {X} blank field, which is what i am trying to figure out how to write. that being said, the {X} should reference the 'headersList' parameter in the makeHeadersWrapper signature. is there any way to do this to reference the value of the variable as {X} and having it be a codemodel JExpr(ession)? please help me solve for {X}! :P

thanks sincerely, Piotr

Piotr
  • 1,437
  • 6
  • 19
  • 24
  • Did you mean that the generated code should have this arraylist with all the values in it? Then you could create the JVar and initialize it. But still you'll have to loop through it. – Syam S May 19 '14 at 15:44
  • Syam S, that is exactly what i mean. i would like it to work as it does with say JExpr.lit( somePrimitive ). i know JVar is the way to go, however is there definitely no way around looping through it? i'm looking for a pass-by-value type of solution, only in this case the by "value" i mean the contents of the ArrayList. thanks! :) – Piotr May 19 '14 at 17:00
  • I don't think there is a way just like you think. JVar supports literals and array but not arraylist. Even with JArray you'll have to do looping or method chaining. :) – Syam S May 19 '14 at 17:09

1 Answers1

3

Basically I don't think JExpression supports Lists. It does have a JArray but still you'll have to iterate over the headersList. Up to codemode-2.6 there is no method in JExpr or JExpression that iterates over a list. So you cant get away with the iteration part. So with that the standard practice would be iterate over the headersList and add initialization statements.

Say for example you have a Namevalue pair like

public class BasicNameValuePair {
    private String name;
    private String value;

    public BasicNameValuePair(String name, String value) {
        this.name = name;
        this.value = value;
    }
    //getters & setters
}

and initialized like

List<BasicNameValuePair> headerList = new ArrayList<BasicNameValuePair>();
headerList.add(new BasicNameValuePair("1", "1"));
headerList.add(new BasicNameValuePair("2", "2"));
headerList.add(new BasicNameValuePair("3", "3"));
headerList.add(new BasicNameValuePair("4", "4"));

Then you could generate you method body as

private JMethod makeHeadersWrapper( String endPointName, List<BasicNameValuePair> headersList ) {

    JClass headerClass = codeModel.ref( headersList.getClass() ).narrow( BasicNameValuePair.class );
    JMethod wrapperMethod = definedClass.method( JMod.PUBLIC | JMod.STATIC, headerClass, ( "make" + endPointName + "Header") );

    JVar headersListVar = wrapperMethod.body().decl( wrapperMethod.type(), "headersList", JExpr._new(headerClass) );
    for(BasicNameValuePair nameValuePair : headersList) {
        wrapperMethod.body().add(headersListVar.invoke("add").arg(JExpr._new(nameValuePairClass).arg(nameValuePair.getName()).arg(nameValuePair.getValue())));
    }
    wrapperMethod.body()._return( headersListVar );

    return wrapperMethod;
}

This will generate the code

public static ArrayList<BasicNameValuePair> makeTestHeader() {
    ArrayList<BasicNameValuePair> headersList = new ArrayList<BasicNameValuePair>();
    headersList.add(new BasicNameValuePair("1", "1"));
    headersList.add(new BasicNameValuePair("2", "2"));
    headersList.add(new BasicNameValuePair("3", "3"));
    headersList.add(new BasicNameValuePair("4", "4"));
    return headersList;
}

However ArrayList have a constructor method using {{ }}. So you could declare like

List<String> lst = new ArrayList<String>(){{ add("1"); add("2"); add("3"); add("4"); }};

And JExpr has a direct() method wherein we could pass a source code directly. Using both of these we could generate the code like below. I wont recommend this approach, but its possible.

private JMethod makeHeadersWrapper( String endPointName, List<BasicNameValuePair> headersList ) {
    JMethod wrapperMethod = definedClass.method( JMod.PUBLIC | JMod.STATIC, codeModel.ref( headersList.getClass() ).narrow( BasicNameValuePair.class ), ( "make" + endPointName + "Header") );

    JVar headersListVar = wrapperMethod.body().decl( wrapperMethod.type(), "headersList", JExpr.direct(generateListConstructor(headersList)) );
    wrapperMethod.body()._return( headersListVar );

    return wrapperMethod;

}

private String generateListConstructor(List<BasicNameValuePair> headersList) {
    StringBuilder listEpr = new StringBuilder("new ArrayList<scope.BasicNameValuePair>(){{");
    for(BasicNameValuePair nameValuePair : headersList) {
        listEpr.append("add(new BasicNameValuePair(\"").append(nameValuePair.getName()).append("\", \"").append(nameValuePair.getValue()).append("\")); ");
    }
    return listEpr.append("}}").toString();
}

This will generate

public static ArrayList<BasicNameValuePair> makeTestHeader() {
    ArrayList<BasicNameValuePair> headersList = (new ArrayList<scope.BasicNameValuePair>(){{add(new BasicNameValuePair("1", "1")); add(new BasicNameValuePair("2", "2")); add(new BasicNameValuePair("3", "3")); add(new BasicNameValuePair("4", "4")); }});
    return headersList;
}

This is also a valid java code.

This answer may not be the exact one you are looking for but it may give you an idea to progress on. Hope this helps.

Syam S
  • 8,421
  • 1
  • 26
  • 36
  • hey Syam S, thanks for your solution. are you positive there is no way around traversing and copying values though? :/ – Piotr May 22 '14 at 14:01
  • Yes. Almost certain. I've gone through the source code of codemodel-2.6.jar and I couldn't find any. Unless someone have a very brilliant workaround.. ;) – Syam S May 22 '14 at 14:13
  • You could serialize your ArrayList into a string representation and then generate a literal of that string into the code. But that would be ugly -- your generated code wouldn't be human-readable. – Anna Dickinson May 22 '14 at 14:24
  • @Anna : Good idea, but that would be very nasty. We may have to use Base64Coder and before using we'll have to deserialize and all that make our code more complex than doing it via a looping. Moreover the generated code will be cryptic. Anyway no iterating though.. good thinking ;) – Syam S May 22 '14 at 14:35
  • Yeah, that wouldn't be a *good* way to do it, but you wouldn't have to write any iterating code. Of course, iteration would still happen behind the scenes as the serialization methods are operating. But, it does answer the question. :-) – Anna Dickinson May 22 '14 at 15:09