BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
njespersen
Calcite | Level 5

Hello

 

from the data step, I can call methods on Java classes with array arguments. A metod with this signature is fine: public void str(String args[]). 

 

However, returning arrays seems to be impossible. I cannot get a call to a method with this signature to work: public String [] str(String args[]). 

 

Should this be possible?

 

If so, how? 

 

Thank you

 

Niels Jespersen

1 ACCEPTED SOLUTION

Accepted Solutions
RichardDeVen
Barite | Level 11

Unfortunately DATA step component object JavaObj does not have a built-in method for returning Java arrays to DATA step arrays.

Should it ? Maybe, but I speculate the demand for such is so low that it does not warrant the development time and support costs.

 

You can pass arrays of either String or Double into Java methods, but any changes made to those arrays in the method do not get reflected back to the DATA step array.

 

An adapter class is needed for the cases when you need an array in Java to be accessed in DATA step. 

  • Tip: You can easily "fiddle around with" (develop) adapter classes in a SAS session by compiling them in a Proc GROOVY step and then accessing them in DATA step via JavaObj.
  • Tip: GROOVY classes need a main() method, otherwise the Proc will log an ERROR and exception org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack

The JavaObj provides a bridge to hosted class methods via its JavaObj methods whose names are:

  • call<type>Method(<method-name>, <arg-1> ... <arg-n>, <method-return>)

 

Fiddling around #1

This code shows an array passed to a Java method and its elements get changed therein.  The changes do not get reflected back to the array in the calling DATA step.

proc groovy;
  clear;   * clean slate;
  submit;
    class Example {
      public String hello() {
        return "Hello from a groovy class";
      }
      public void log (String string) {
        System.out.println("NOTE: Stdout of JVM says: " + string);
      }
      public void fill(String[] target) {
        for (int i=1; i<=target.length; i++)
        {
          target[i-1] = i.toString();
        }
        System.out.println("NOTE: Stdout of JVM says: " +target);
      }
      void main() { 
        // a main is required to prevent groovy errors while 
        // allowing compilation and loading of the class into the JVM
      }
    }
  endsubmit;
quit;


data _null_;
  array number_strings [10] $3 ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu');
  length s $200;

  declare JavaObj j ('Example');

  j.callStringMethod ('hello', s);
  put s=;

  j.callVoidMethod ('log', 'This is a message');

  put 'NOTE: data step array before "fill": ' number_strings(*);

  j.callVoidMethod ('fill', number_strings);

  put 'NOTE: data step array after  "fill": ' number_strings(*);
run;

Log

s=Hello from a groovy class
NOTE: data step array before "fill": abc def ghi jkl mno pqr stu
NOTE: data step array after  "fill": abc def ghi jkl mno pqr stu
NOTE: Stdout of JVM says: This is a message
NOTE: Stdout of JVM says: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Fiddling around #2

Add an adapter class named StringArray to the mix.  The Java array data lives in the adapter instance and the array elements are accessible through the adapter class methods get and set.

proc groovy;
  clear;   * clean slate;
  submit;
class StringArray
{
    private String[] values;

    int getLength () { return (values == null) ? 0 : values.length }

    void setLength (double dlength)
    {
        int length = (dlength >= 0) ? (int) dlength : 0;
        values = new String[length];
    }

    String get (double dindex) // accesor method callable via JavaObj
    {
      int index = (int) dindex;

      String result = 
        values == null
        ? "" 
        : index >= 0 && index < values.length && values[index] != null
          ? values[index] 
          : "";

      return result;
    }

    void set (double dindex, String value) // accesor method callable via JavaObj
    {
      int index = (int) dindex;

      if (values == null)
        return ;
      else
      if (index >= 0 && index < values.length)
        values[index] = value;
    }

    String toString() {
      return values.toString();
    }

    void main() { }
}

class Example {
  public void fill(StringArray target) {
    for (int i=1; i<=target.getLength(); i++)
    {
      target.set(i-1, ((char)(i+64)).toString());
    }
    System.out.println("NOTE: Stdout of JVM. fill() says: " +target.toString());
  }
  void main() { 
    // a main is required to prevent groovy errors while 
    // allowing compilation and loading of the class into the JVM
  }
}
  endsubmit;
quit;

data _null_;
  declare javaobj sa ('StringArray');
  declare javaobj ex ('Example');

  sa.callVoidMethod('setLength', 8);
  ex.callVoidMethod('fill', sa);

  length sa_string $200;
  sa.callStringMethod('toString', sa_string);
  put 'NOTE: ' sa_string=;

  length _5th_element_value $20;
  sa.callStringMethod('get', 4, _5th_element_value);
  put 'NOTE: ' _5th_element_value=;

  sa.callVoidMethod('set', 4, "It works!");
  sa.callStringMethod('get', 4, _5th_element_value);
  put 'NOTE: ' _5th_element_value=;
run;

Log

NOTE: sa_string=[A, B, C, D, E, F, G, H]
NOTE: _5th_element_value=E
NOTE: _5th_element_value=It works!
NOTE: Stdout of JVM. fill() says: [A, B, C, D, E, F, G, H]

View solution in original post

3 REPLIES 3
RichardDeVen
Barite | Level 11

Unfortunately DATA step component object JavaObj does not have a built-in method for returning Java arrays to DATA step arrays.

Should it ? Maybe, but I speculate the demand for such is so low that it does not warrant the development time and support costs.

 

You can pass arrays of either String or Double into Java methods, but any changes made to those arrays in the method do not get reflected back to the DATA step array.

 

An adapter class is needed for the cases when you need an array in Java to be accessed in DATA step. 

  • Tip: You can easily "fiddle around with" (develop) adapter classes in a SAS session by compiling them in a Proc GROOVY step and then accessing them in DATA step via JavaObj.
  • Tip: GROOVY classes need a main() method, otherwise the Proc will log an ERROR and exception org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack

The JavaObj provides a bridge to hosted class methods via its JavaObj methods whose names are:

  • call<type>Method(<method-name>, <arg-1> ... <arg-n>, <method-return>)

 

Fiddling around #1

This code shows an array passed to a Java method and its elements get changed therein.  The changes do not get reflected back to the array in the calling DATA step.

proc groovy;
  clear;   * clean slate;
  submit;
    class Example {
      public String hello() {
        return "Hello from a groovy class";
      }
      public void log (String string) {
        System.out.println("NOTE: Stdout of JVM says: " + string);
      }
      public void fill(String[] target) {
        for (int i=1; i<=target.length; i++)
        {
          target[i-1] = i.toString();
        }
        System.out.println("NOTE: Stdout of JVM says: " +target);
      }
      void main() { 
        // a main is required to prevent groovy errors while 
        // allowing compilation and loading of the class into the JVM
      }
    }
  endsubmit;
quit;


data _null_;
  array number_strings [10] $3 ('abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu');
  length s $200;

  declare JavaObj j ('Example');

  j.callStringMethod ('hello', s);
  put s=;

  j.callVoidMethod ('log', 'This is a message');

  put 'NOTE: data step array before "fill": ' number_strings(*);

  j.callVoidMethod ('fill', number_strings);

  put 'NOTE: data step array after  "fill": ' number_strings(*);
run;

Log

s=Hello from a groovy class
NOTE: data step array before "fill": abc def ghi jkl mno pqr stu
NOTE: data step array after  "fill": abc def ghi jkl mno pqr stu
NOTE: Stdout of JVM says: This is a message
NOTE: Stdout of JVM says: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Fiddling around #2

Add an adapter class named StringArray to the mix.  The Java array data lives in the adapter instance and the array elements are accessible through the adapter class methods get and set.

proc groovy;
  clear;   * clean slate;
  submit;
class StringArray
{
    private String[] values;

    int getLength () { return (values == null) ? 0 : values.length }

    void setLength (double dlength)
    {
        int length = (dlength >= 0) ? (int) dlength : 0;
        values = new String[length];
    }

    String get (double dindex) // accesor method callable via JavaObj
    {
      int index = (int) dindex;

      String result = 
        values == null
        ? "" 
        : index >= 0 && index < values.length && values[index] != null
          ? values[index] 
          : "";

      return result;
    }

    void set (double dindex, String value) // accesor method callable via JavaObj
    {
      int index = (int) dindex;

      if (values == null)
        return ;
      else
      if (index >= 0 && index < values.length)
        values[index] = value;
    }

    String toString() {
      return values.toString();
    }

    void main() { }
}

class Example {
  public void fill(StringArray target) {
    for (int i=1; i<=target.getLength(); i++)
    {
      target.set(i-1, ((char)(i+64)).toString());
    }
    System.out.println("NOTE: Stdout of JVM. fill() says: " +target.toString());
  }
  void main() { 
    // a main is required to prevent groovy errors while 
    // allowing compilation and loading of the class into the JVM
  }
}
  endsubmit;
quit;

data _null_;
  declare javaobj sa ('StringArray');
  declare javaobj ex ('Example');

  sa.callVoidMethod('setLength', 8);
  ex.callVoidMethod('fill', sa);

  length sa_string $200;
  sa.callStringMethod('toString', sa_string);
  put 'NOTE: ' sa_string=;

  length _5th_element_value $20;
  sa.callStringMethod('get', 4, _5th_element_value);
  put 'NOTE: ' _5th_element_value=;

  sa.callVoidMethod('set', 4, "It works!");
  sa.callStringMethod('get', 4, _5th_element_value);
  put 'NOTE: ' _5th_element_value=;
run;

Log

NOTE: sa_string=[A, B, C, D, E, F, G, H]
NOTE: _5th_element_value=E
NOTE: _5th_element_value=It works!
NOTE: Stdout of JVM. fill() says: [A, B, C, D, E, F, G, H]
njespersen
Calcite | Level 5
Thanks a lot. Impressive work. My original problem found another solution not involving SAS or Java. Hopefully your work can be used by others with similar problems.

Regards Niels Jespersen
RichardDeVen
Barite | Level 11

Thanks Niels.

I developed the concepts quite a while ago for the SUGI 30 conference paper "Java in SAS®:
JavaObj, a DATA Step Component Object" and demonstration code jDSGI

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!
How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 3 replies
  • 679 views
  • 1 like
  • 2 in conversation